From 29abe30d518afd860b7146f743511fadfc34f93f Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 14:53:20 -0400 Subject: [PATCH 01/24] more chat teams split --- .../chat/conversation/info-panel/common.tsx | 10 +---- .../chat/conversation/info-panel/members.tsx | 2 +- .../input-area/suggestors/users.tsx | 37 ++++++++----------- shared/chat/conversation/normal/container.tsx | 3 ++ shared/chat/new-team-dialog-container.tsx | 11 ++++-- shared/constants/init/shared.tsx | 10 +---- shared/stores/chat.tsx | 36 ++++-------------- shared/stores/teams.tsx | 13 ------- shared/teams/channel/index.tsx | 10 ++--- shared/teams/team-members.tsx | 31 ++++++++++++++++ 10 files changed, 75 insertions(+), 88 deletions(-) create mode 100644 shared/teams/team-members.tsx diff --git a/shared/chat/conversation/info-panel/common.tsx b/shared/chat/conversation/info-panel/common.tsx index 0e3b9a65297f..5d9352307d37 100644 --- a/shared/chat/conversation/info-panel/common.tsx +++ b/shared/chat/conversation/info-panel/common.tsx @@ -1,9 +1,8 @@ import type * as Chat from '@/stores/chat' import {useTeamsState} from '@/stores/teams' -import * as React from 'react' import * as Styles from '@/styles' import type * as T from '@/constants/types' -import * as C from '@/constants' +import {useLoadTeamMembers} from '@/teams/team-members' export const infoPanelWidthElectron = 320 const infoPanelWidthPhone = Styles.dimensionWidth @@ -22,12 +21,7 @@ export function infoPanelWidth() { const isBot = (type: T.Teams.TeamRoleType) => type === 'bot' || type === 'restrictedbot' export const useTeamHumans = (teamID: T.Teams.TeamID) => { - const [lastTID, setLastTID] = React.useState('') - const getMembers = useTeamsState(s => s.dispatch.getMembers) - if (lastTID !== teamID) { - setLastTID(teamID) - C.ignorePromise(getMembers(teamID)) - } + useLoadTeamMembers(teamID, !!teamID) const teamMembers = useTeamsState(s => s.teamIDToMembers.get(teamID)) const bots = (() => { const ret = new Set() diff --git a/shared/chat/conversation/info-panel/members.tsx b/shared/chat/conversation/info-panel/members.tsx index 946dec8a5491..8e0014b12ad5 100644 --- a/shared/chat/conversation/info-panel/members.tsx +++ b/shared/chat/conversation/info-panel/members.tsx @@ -46,7 +46,7 @@ const MembersTab = (props: Props) => { const refreshParticipants = C.useRPC(T.RPCChat.localRefreshParticipantsRpcPromise) const participantInfo = Chat.useChatContext(s => s.participants) const participants = Chat.useChatContext( - C.useShallow(s => Chat.getBotsAndParticipants(s.meta, s.participants).participants) + C.useShallow(s => Chat.getBotsAndParticipants(s.meta, s.participants, teamMembers).participants) ) const [lastTeamName, setLastTeamName] = React.useState('') React.useEffect(() => { diff --git a/shared/chat/conversation/input-area/suggestors/users.tsx b/shared/chat/conversation/input-area/suggestors/users.tsx index 5651f081784e..e0319a81dcbb 100644 --- a/shared/chat/conversation/input-area/suggestors/users.tsx +++ b/shared/chat/conversation/input-area/suggestors/users.tsx @@ -1,10 +1,10 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' -import {useTeamsState} from '@/stores/teams' import * as T from '@/constants/types' import * as Common from './common' import * as Kb from '@/common-adapters' import {useUsersState} from '@/stores/users' +import {useTeamMembers} from '@/teams/team-members' export const transformer = ( input: { @@ -139,27 +139,22 @@ const getTeams = (layout?: T.RPCChat.UIInboxLayout) => { const useDataUsers = () => { const infoMap = useUsersState(s => s.infoMap) - const participantInfo = Chat.useChatContext(s => s.participants) - return Chat.useChatContext( - C.useDeep(s => { - const {teamID, teamType} = s.meta - // TODO not reactive - const teamMembers = useTeamsState.getState().teamIDToMembers.get(teamID) - const usernames = teamMembers - ? [...teamMembers.values()].map(m => m.username).sort((a, b) => a.localeCompare(b)) - : participantInfo.all - const suggestions = usernames.map(username => ({ - fullName: infoMap.get(username)?.fullname || '', - username, - })) - if (teamType !== 'adhoc') { - const fullName = teamType === 'small' ? 'Everyone in this team' : 'Everyone in this channel' - suggestions.push({fullName, username: 'channel'}, {fullName, username: 'here'}) - } - // TODO this will thrash on every store change, TODO fix - return suggestions - }) + const {participantInfo, teamID, teamType} = Chat.useChatContext( + C.useShallow(s => ({participantInfo: s.participants, teamID: s.meta.teamID, teamType: s.meta.teamType})) ) + const teamMembers = useTeamMembers(teamID) + const usernames = teamMembers + ? [...teamMembers.values()].map(member => member.username).sort((a, b) => a.localeCompare(b)) + : participantInfo.all + const suggestions = usernames.map(username => ({ + fullName: infoMap.get(username)?.fullname || '', + username, + })) + if (teamType !== 'adhoc') { + const fullName = teamType === 'small' ? 'Everyone in this team' : 'Everyone in this channel' + suggestions.push({fullName, username: 'channel'}, {fullName, username: 'here'}) + } + return suggestions } const useDataTeams = () => { diff --git a/shared/chat/conversation/normal/container.tsx b/shared/chat/conversation/normal/container.tsx index a64fb1e8da49..3d5848969f48 100644 --- a/shared/chat/conversation/normal/container.tsx +++ b/shared/chat/conversation/normal/container.tsx @@ -6,6 +6,7 @@ import Normal from '.' import * as T from '@/constants/types' import {FocusProvider, ScrollProvider} from './context' import {OrangeLineContext} from '../orange-line-context' +import {useLoadTeamMembers} from '@/teams/team-members' const useOrangeLine = () => { const [orangeLine, setOrangeLine] = React.useState(T.Chat.numberToOrdinal(0)) @@ -84,6 +85,8 @@ const useOrangeLine = () => { } const NormalWrapper = function NormalWrapper() { + const {teamID, teamType} = Chat.useChatContext(s => s.meta) + useLoadTeamMembers(teamID, teamType !== 'adhoc') const orangeLine = useOrangeLine() return ( diff --git a/shared/chat/new-team-dialog-container.tsx b/shared/chat/new-team-dialog-container.tsx index 1cfe0ba2c5b5..f7cd15e1ea66 100644 --- a/shared/chat/new-team-dialog-container.tsx +++ b/shared/chat/new-team-dialog-container.tsx @@ -2,17 +2,22 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' import {CreateNewTeam} from '../teams/new-team' import {useTeamsState} from '@/stores/teams' +import {useCurrentUserState} from '@/stores/current-user' const NewTeamDialog = () => { - const conversationIDKey = Chat.useChatContext(s => s.id) const baseTeam = '' const navigateUp = C.Router2.navigateUp const onCancel = () => { navigateUp() } - const createNewTeamFromConversation = useTeamsState(s => s.dispatch.createNewTeamFromConversation) + const participantInfo = Chat.useChatContext(s => s.participants) + const username = useCurrentUserState(s => s.username) + const createNewTeam = useTeamsState(s => s.dispatch.createNewTeam) const onSubmit = (teamname: string) => { - createNewTeamFromConversation(conversationIDKey, teamname) + const users = participantInfo.name + .filter(participant => participant !== username) + .map(assertion => ({assertion, role: 'writer' as const})) + createNewTeam(teamname, false, true, {sendChatNotification: true, users}) } const props = { baseTeam, diff --git a/shared/constants/init/shared.tsx b/shared/constants/init/shared.tsx index fa36a49a946b..a3bafdebb649 100644 --- a/shared/constants/init/shared.tsx +++ b/shared/constants/init/shared.tsx @@ -166,18 +166,12 @@ export const initTeamBuildingCallbacks = () => { } } -export const initChat2Callbacks = () => { +export const initChatRetentionCallbacks = () => { const currentState = useChatState.getState() useChatState.setState({ dispatch: { ...currentState.dispatch, defer: { - onGetTeamsTeamIDToMembers: (teamID: T.Teams.TeamID) => { - return storeRegistry.getState('teams').teamIDToMembers.get(teamID) - }, - onTeamsGetMembers: async (teamID: T.Teams.TeamID) => { - return storeRegistry.getState('teams').dispatch.getMembers(teamID) - }, onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => { storeRegistry.getState('teams').dispatch.updateTeamRetentionPolicy(metas) }, @@ -455,7 +449,7 @@ export const initSharedSubscriptions = () => { }) ) - initChat2Callbacks() + initChatRetentionCallbacks() initTeamBuildingCallbacks() } diff --git a/shared/stores/chat.tsx b/shared/stores/chat.tsx index e9b8166542a1..b81f3026f9d3 100644 --- a/shared/stores/chat.tsx +++ b/shared/stores/chat.tsx @@ -54,28 +54,27 @@ export const getMessageKey = (message: T.Chat.Message) => export const getBotsAndParticipants = ( meta: T.Immutable, participantInfo: T.Immutable, + teamMembers?: ReadonlyMap, sort?: boolean ) => { const isAdhocTeam = meta.teamType === 'adhoc' - const teamMembers = - useChatState.getState().dispatch.defer.onGetTeamsTeamIDToMembers(meta.teamID) ?? - new Map() + const members = teamMembers ?? new Map() let bots: Array = [] if (isAdhocTeam) { bots = participantInfo.all.filter(p => !participantInfo.name.includes(p)) } else { - bots = [...teamMembers.values()] + bots = [...members.values()] .filter( p => - TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'restrictedbot') || - TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p.username, 'bot') + TeamConstants.userIsRoleInTeamWithInfo(members, p.username, 'restrictedbot') || + TeamConstants.userIsRoleInTeamWithInfo(members, p.username, 'bot') ) .map(p => p.username) .sort((l, r) => l.localeCompare(r)) } let participants: ReadonlyArray = participantInfo.all if (meta.channelname === 'general') { - participants = [...teamMembers.values()].reduce>((l, mi) => { + participants = [...members.values()].reduce>((l, mi) => { l.push(mi.username) return l }, []) @@ -84,8 +83,8 @@ export const getBotsAndParticipants = ( participants = sort ? participants .map(p => ({ - isAdmin: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'admin') : false, - isOwner: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(teamMembers, p, 'owner') : false, + isAdmin: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(members, p, 'admin') : false, + isOwner: !isAdhocTeam ? TeamConstants.userIsRoleInTeamWithInfo(members, p, 'owner') : false, username: p, })) .sort((l, r) => { @@ -219,10 +218,6 @@ export type State = Store & { badgesUpdated: (badgeState?: T.RPCGen.BadgeState) => void clearMetas: () => void defer: { - onGetTeamsTeamIDToMembers: ( - teamID: T.Teams.TeamID - ) => ReadonlyMap | undefined - onTeamsGetMembers: (teamID: T.Teams.TeamID) => Promise onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => void } createConversation: (participants: ReadonlyArray, highlightMessageID?: T.Chat.MessageID) => void @@ -367,12 +362,6 @@ export const useChatState = Z.createZustand('chat', (set, get) => { ignorePromise(f()) }, defer: { - onGetTeamsTeamIDToMembers: (_teamID: T.Teams.TeamID) => { - throw new Error('onGetTeamsTeamIDToMembers not properly initialized') - }, - onTeamsGetMembers: (_teamID: T.Teams.TeamID) => { - throw new Error('onTeamsGetMembers not properly initialized') - }, onTeamsUpdateTeamRetentionPolicy: (_metas: ReadonlyArray) => { throw new Error('onTeamsUpdateTeamRetentionPolicy not properly initialized') }, @@ -523,15 +512,6 @@ export const useChatState = Z.createZustand('chat', (set, get) => { dispatch.setMeta(m) } }) - - const selectedConversation = Common.getSelectedConversation() - const {isMetaGood, meta} = storeRegistry.getConvoState(selectedConversation) - if (isMetaGood()) { - const {teamID} = meta - if (!get().dispatch.defer.onGetTeamsTeamIDToMembers(teamID) && meta.teamname) { - ignorePromise(get().dispatch.defer.onTeamsGetMembers(teamID)) - } - } }, onChatInboxSynced: action => { const {syncRes} = action.payload.params diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 1e14889ed580..d0d2ab0ddb29 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -21,7 +21,6 @@ import {mapGetEnsureValue} from '@/util/map' import {bodyToJSON} from '@/constants/rpc-utils' import {fixCrop} from '@/util/crop' import {getTBStore} from '@/stores/team-building' -import {storeRegistry} from '@/stores/store-registry' import {useConfigState} from '@/stores/config' import {useCurrentUserState} from '@/stores/current-user' import {useUsersState} from '@/stores/users' @@ -863,7 +862,6 @@ export type State = Store & { fromTeamBuilder?: boolean } ) => void - createNewTeamFromConversation: (conversationIDKey: T.Chat.ConversationIDKey, teamname: string) => void deleteChannelConfirmed: (teamID: T.Teams.TeamID, conversationIDKey: T.Chat.ConversationIDKey) => void deleteMultiChannelsConfirmed: (teamID: T.Teams.TeamID, channels: Array) => void deleteTeam: (teamID: T.Teams.TeamID) => void @@ -1285,17 +1283,6 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { } ignorePromise(f()) }, - createNewTeamFromConversation: (conversationIDKey, teamname) => { - const me = useCurrentUserState.getState().username - const participantInfo = storeRegistry.getConvoState(conversationIDKey).participants - // exclude bots from the newly created team, they can be added back later. - const participants = participantInfo.name.filter(p => p !== me) // we will already be in as 'owner' - const users = participants.map(assertion => ({ - assertion, - role: assertion === me ? ('admin' as const) : ('writer' as const), - })) - get().dispatch.createNewTeam(teamname, false, true, {sendChatNotification: true, users}) - }, deleteChannelConfirmed: (teamID, conversationIDKey) => { const f = async () => { // channelName is only needed for confirmation, so since we handle diff --git a/shared/teams/channel/index.tsx b/shared/teams/channel/index.tsx index 5e503f15bbbf..e7d668ab892f 100644 --- a/shared/teams/channel/index.tsx +++ b/shared/teams/channel/index.tsx @@ -16,6 +16,7 @@ import BotRow from '../team/rows/bot-row/bot' import SettingsList from '../../chat/conversation/info-panel/settings' import EmptyRow from '../team/rows/empty-row' import {useUsersState} from '@/stores/users' +import {useLoadTeamMembers} from '@/teams/team-members' export type OwnProps = { teamID: T.Teams.TeamID @@ -31,26 +32,23 @@ const useLoadDataForChannelPage = ( participants: ReadonlyArray ) => { const prevSelectedTabRef = React.useRef(selectedTab) - const getMembers = Teams.useTeamsState(s => s.dispatch.getMembers) const getBlockState = useUsersState(s => s.dispatch.getBlockState) const unboxRows = Chat.useChatState(s => s.dispatch.unboxRows) + useLoadTeamMembers(teamID, ['bots', 'members', 'settings'].includes(selectedTab)) React.useEffect(() => { if (selectedTab !== prevSelectedTabRef.current && selectedTab === 'members') { if (meta.conversationIDKey === 'EMPTY') { unboxRows([conversationIDKey]) } - C.ignorePromise(getMembers(teamID)) getBlockState(participants) } }, [ unboxRows, getBlockState, - getMembers, selectedTab, conversationIDKey, meta.conversationIDKey, participants, - teamID, ]) React.useEffect(() => { @@ -120,13 +118,13 @@ const Channel = (props: OwnProps) => { const providedTab = props.selectedTab const meta = Chat.useConvoState(conversationIDKey, s => s.meta) + const teamMembers = Teams.useTeamsState(s => s.teamIDToMembers.get(teamID) ?? emptyMapForUseSelector) const {bots, participants: _participants} = Chat.useConvoState( conversationIDKey, - C.useDeep(s => Chat.getBotsAndParticipants(meta, s.participants, true /* sort */)) + C.useDeep(s => Chat.getBotsAndParticipants(meta, s.participants, teamMembers, true /* sort */)) ) const yourOperations = Teams.useTeamsState(s => Teams.getCanPerformByID(s, teamID)) const isPreview = meta.membershipType === 'youArePreviewing' || meta.membershipType === 'notMember' - const teamMembers = Teams.useTeamsState(s => s.teamIDToMembers.get(teamID) ?? emptyMapForUseSelector) const [selectedTab, setSelectedTab] = useTabsState(conversationIDKey, providedTab) useLoadDataForChannelPage(teamID, conversationIDKey, selectedTab, meta, _participants) const participants = useChannelParticipants(teamID, conversationIDKey) diff --git a/shared/teams/team-members.tsx b/shared/teams/team-members.tsx new file mode 100644 index 000000000000..7c34deb75640 --- /dev/null +++ b/shared/teams/team-members.tsx @@ -0,0 +1,31 @@ +import * as C from '@/constants' +import type * as T from '@/constants/types' +import {useTeamsState} from '@/stores/teams' +import * as React from 'react' + +export const useLoadTeamMembers = (teamID: T.Teams.TeamID, enabled = true) => { + const lastRequestedTeamIDRef = React.useRef() + const getMembers = useTeamsState(s => s.dispatch.getMembers) + const missing = useTeamsState(s => enabled && !!teamID && !s.teamIDToMembers.has(teamID)) + + React.useEffect(() => { + if (!enabled || !teamID) { + lastRequestedTeamIDRef.current = undefined + return + } + if (!missing) { + if (lastRequestedTeamIDRef.current === teamID) { + lastRequestedTeamIDRef.current = undefined + } + return + } + if (lastRequestedTeamIDRef.current === teamID) { + return + } + lastRequestedTeamIDRef.current = teamID + C.ignorePromise(getMembers(teamID)) + }, [enabled, getMembers, missing, teamID]) +} + +export const useTeamMembers = (teamID: T.Teams.TeamID) => + useTeamsState(s => s.teamIDToMembers.get(teamID)) From 7f3217f2ab304a893430901ff59af6c0319e1b4a Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 14:57:32 -0400 Subject: [PATCH 02/24] WIP --- shared/constants/init/shared.tsx | 18 +----------------- shared/stores/chat.tsx | 9 --------- shared/stores/teams.tsx | 26 ++++++++++++++------------ 3 files changed, 15 insertions(+), 38 deletions(-) diff --git a/shared/constants/init/shared.tsx b/shared/constants/init/shared.tsx index a3bafdebb649..2798ad4037fd 100644 --- a/shared/constants/init/shared.tsx +++ b/shared/constants/init/shared.tsx @@ -166,20 +166,6 @@ export const initTeamBuildingCallbacks = () => { } } -export const initChatRetentionCallbacks = () => { - const currentState = useChatState.getState() - useChatState.setState({ - dispatch: { - ...currentState.dispatch, - defer: { - onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => { - storeRegistry.getState('teams').dispatch.updateTeamRetentionPolicy(metas) - }, - }, - }, - }) -} - export const initSharedSubscriptions = () => { // HMR cleanup: unsubscribe old store subscriptions before re-subscribing for (const unsub of _sharedUnsubs) unsub() @@ -448,8 +434,6 @@ export const initSharedSubscriptions = () => { storeRegistry.getState('chat').dispatch.onRouteChanged(prev, next) }) ) - - initChatRetentionCallbacks() initTeamBuildingCallbacks() } @@ -489,6 +473,7 @@ export const _onEngineIncoming = (action: EngineGen.Actions) => { case 'chat.1.chatUi.chatShowManageChannels': case 'keybase.1.NotifyTeam.teamMetadataUpdate': case 'chat.1.NotifyChat.ChatWelcomeMessageLoaded': + case 'chat.1.NotifyChat.ChatSetTeamRetention': case 'keybase.1.NotifyTeam.teamTreeMembershipsPartial': case 'keybase.1.NotifyTeam.teamTreeMembershipsDone': case 'keybase.1.NotifyTeam.teamRoleMapChanged': @@ -582,7 +567,6 @@ export const _onEngineIncoming = (action: EngineGen.Actions) => { case 'chat.1.NotifyChat.NewChatActivity': case 'chat.1.NotifyChat.ChatTypingUpdate': case 'chat.1.NotifyChat.ChatSetConvRetention': - case 'chat.1.NotifyChat.ChatSetTeamRetention': { const {useChatState} = require('@/stores/chat') as typeof UseChatStateType useChatState.getState().dispatch.onEngineIncomingImpl(action) diff --git a/shared/stores/chat.tsx b/shared/stores/chat.tsx index b81f3026f9d3..cfc22b086788 100644 --- a/shared/stores/chat.tsx +++ b/shared/stores/chat.tsx @@ -217,9 +217,6 @@ export type State = Store & { dispatch: { badgesUpdated: (badgeState?: T.RPCGen.BadgeState) => void clearMetas: () => void - defer: { - onTeamsUpdateTeamRetentionPolicy: (metas: ReadonlyArray) => void - } createConversation: (participants: ReadonlyArray, highlightMessageID?: T.Chat.MessageID) => void ensureWidgetMetas: () => void inboxRefresh: (reason: RefreshReason) => void @@ -361,11 +358,6 @@ export const useChatState = Z.createZustand('chat', (set, get) => { } ignorePromise(f()) }, - defer: { - onTeamsUpdateTeamRetentionPolicy: (_metas: ReadonlyArray) => { - throw new Error('onTeamsUpdateTeamRetentionPolicy not properly initialized') - }, - }, ensureWidgetMetas: () => { const {inboxLayout} = get() if (!inboxLayout?.widgetList) { @@ -872,7 +864,6 @@ export const useChatState = Z.createZustand('chat', (set, get) => { cs.dispatch.setMeta(meta) } }) - get().dispatch.defer.onTeamsUpdateTeamRetentionPolicy(metas) } else { logger.error( 'got NotifyChat.ChatSetTeamRetention with no attached InboxUIItems. The local version may be out of date' diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index d0d2ab0ddb29..632de37b8e24 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -987,7 +987,6 @@ export type State = Store & { sendChatNotification: boolean, crop?: T.RPCGen.ImageCropRect ) => void - updateTeamRetentionPolicy: (metas: ReadonlyArray) => void } } @@ -1922,6 +1921,20 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { get().dispatch.loadedWelcomeMessage(teamID, message) break } + case 'chat.1.NotifyChat.ChatSetTeamRetention': { + const first = action.payload.params.convs?.[0] + if (!first) { + logger.warn('Got ChatSetTeamRetention with no convs; aborting. Local copy may be out of date') + break + } + const teamRetentionPolicy = first.teamRetention + ? Util.serviceRetentionPolicyToRetentionPolicy(first.teamRetention) + : Util.makeRetentionPolicy() + set(s => { + s.teamIDToRetentionPolicy.set(first.tlfID, teamRetentionPolicy) + }) + break + } case 'keybase.1.NotifyTeam.teamTreeMembershipsPartial': { const {membership} = action.payload.params get().dispatch.notifyTreeMembershipsPartial(membership) @@ -2519,17 +2532,6 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { await T.RPCChat.localPostMetadataRpcPromise(param, S.waitingKeyTeamsUpdateChannelName(teamID)) } catch {} }, - updateTeamRetentionPolicy: metas => { - const first = metas[0] - if (!first) { - logger.warn('Got updateTeamRetentionPolicy with no convs; aborting. Local copy may be out of date') - return - } - const {teamRetentionPolicy, teamID} = first - set(s => { - s.teamIDToRetentionPolicy.set(teamID, teamRetentionPolicy) - }) - }, updateTopic: async (teamID, conversationIDKey, newTopic) => { const param = { conversationID: T.Chat.keyToConversationID(conversationIDKey), From 102a456e3b2e2acb1500413e389977eae4880493 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:01:49 -0400 Subject: [PATCH 03/24] WIP --- shared/constants/init/shared.tsx | 2 -- shared/stores/convostate.tsx | 20 +++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/shared/constants/init/shared.tsx b/shared/constants/init/shared.tsx index 2798ad4037fd..8143d85123cb 100644 --- a/shared/constants/init/shared.tsx +++ b/shared/constants/init/shared.tsx @@ -177,8 +177,6 @@ export const initSharedSubscriptions = () => { storeRegistry.getState('chat').inboxLayout?.smallTeams?.[0]?.convID, chatInboxRefresh: reason => storeRegistry.getState('chat').dispatch.inboxRefresh(reason), chatMetasReceived: metas => storeRegistry.getState('chat').dispatch.metasReceived(metas), - chatNavigateToInbox: Util.navigateToInbox, - chatPreviewConversation: Util.previewConversation, chatUnboxRows: (convIDs, force) => storeRegistry.getState('chat').dispatch.unboxRows(convIDs, force), teamsGetMembers: async teamID => storeRegistry.getState('teams').dispatch.getMembers(teamID), usersGetBio: username => storeRegistry.getState('users').dispatch.getBio(username), diff --git a/shared/stores/convostate.tsx b/shared/stores/convostate.tsx index a2114529eb66..26c065014ebf 100644 --- a/shared/stores/convostate.tsx +++ b/shared/stores/convostate.tsx @@ -5,8 +5,10 @@ import * as PlatformSpecific from '@/util/platform-specific' import { clearModals, navigateAppend, + navigateToInbox, navigateUp, navUpToScreen, + previewConversation, switchTab, getVisibleScreen, getModalStack, @@ -254,8 +256,6 @@ export interface ConvoState extends ConvoStore { chatInboxLayoutSmallTeamsFirstConvID: () => T.Chat.ConversationIDKey | undefined chatInboxRefresh: (reason: RefreshReason) => void chatMetasReceived: (metas: ReadonlyArray) => void - chatNavigateToInbox: () => void - chatPreviewConversation: (p: PreviewConversationParams) => void chatUnboxRows: (convIDs: ReadonlyArray, force: boolean) => void teamsGetMembers: (teamID: T.RPCGen.TeamID) => Promise usersGetBio: (username: string) => void @@ -434,12 +434,6 @@ const stubDefer: ConvoState['dispatch']['defer'] = { chatMetasReceived: () => { throw new Error('convostate defer not initialized') }, - chatNavigateToInbox: () => { - throw new Error('convostate defer not initialized') - }, - chatPreviewConversation: () => { - throw new Error('convostate defer not initialized') - }, chatUnboxRows: () => { throw new Error('convostate defer not initialized') }, @@ -777,7 +771,7 @@ const createSlice = const onClick = () => { useConfigState.getState().dispatch.showMain() - get().dispatch.defer.chatNavigateToInbox() + navigateToInbox() get().dispatch.navigateToThread('desktopNotification') } const onClose = () => {} @@ -1633,7 +1627,7 @@ const createSlice = }, blockConversation: reportUser => { const f = async () => { - get().dispatch.defer.chatNavigateToInbox() + navigateToInbox() useConfigState.getState().dispatch.defer.persistRoute?.(false, false) await T.RPCChat.localSetConversationStatusLocalRpcPromise({ conversationID: get().getConvID(), @@ -1736,7 +1730,7 @@ const createSlice = // Nav to inbox but don't use findNewConversation since changeSelectedConversation // does that with better information. It knows the conversation is hidden even before // that state bounces back. - get().dispatch.defer.chatNavigateToInbox() + navigateToInbox() get().dispatch.showInfoPanel(false, undefined) } @@ -2055,7 +2049,7 @@ const createSlice = // no longer in team if (error.code === T.RPCGen.StatusCode.scchatnotinteam) { get().dispatch.defer.chatInboxRefresh('maybeKickedFromTeam') - get().dispatch.defer.chatNavigateToInbox() + navigateToInbox() } if (error.code !== T.RPCGen.StatusCode.scteamreaderror) { // scteamreaderror = user is not in team. they'll see the rekey screen so don't throw for that @@ -2827,7 +2821,7 @@ const createSlice = // remove all bad people const goodParticipants = new Set(participantInfo.all) meta.resetParticipants.forEach(r => goodParticipants.delete(r)) - get().dispatch.defer.chatPreviewConversation({ + previewConversation({ participants: [...goodParticipants], reason: 'resetChatWithoutThem', }) From cb46e026b30a47aa6898ef4d7ea6ced41bad16c5 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:17:53 -0400 Subject: [PATCH 04/24] WIP --- shared/chat/conversation/bot/confirm.tsx | 18 +++-- shared/chat/conversation/bot/install.tsx | 84 ++++++++++++++++++++++++ shared/constants/init/shared.tsx | 1 - shared/stores/convostate.tsx | 36 ---------- 4 files changed, 98 insertions(+), 41 deletions(-) diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index 3a5e107eb485..d4b0194a559b 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -2,7 +2,7 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' import * as Chat from '@/stores/chat' import type * as T from '@/constants/types' -import {useBotConversationIDKey} from './install' +import {useBotConversationIDKey, useRefreshBotMembershipOnSuccess} from './install' type Props = { botUsername: string @@ -10,16 +10,26 @@ type Props = { conversationIDKey?: T.Chat.ConversationIDKey } -const ConfirmBotRemoveImpl = (props: {botUsername: string}) => { - const {botUsername} = props +const ConfirmBotRemoveImpl = (props: {botUsername: string; teamID: T.Teams.TeamID}) => { + const {botUsername, teamID} = props const clearModals = C.Router2.clearModals + const error = C.Waiting.useAnyErrors(C.waitingKeyChatBotRemove) const removeBotMember = Chat.useChatContext(s => s.dispatch.removeBotMember) + const conversationIDKey = Chat.useChatContext(s => s.id) const onClose = () => { clearModals() } const onRemove = () => { removeBotMember(botUsername) } + useRefreshBotMembershipOnSuccess( + conversationIDKey, + teamID, + C.waitingKeyChatBotRemove, + error, + true, + clearModals + ) return ( { const conversationIDKey = useBotConversationIDKey(props.conversationIDKey, teamID) return conversationIDKey ? ( - + ) : null } diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index fe61338060e9..a8c2743d0af1 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -10,9 +10,78 @@ import {openURL} from '@/util/misc' import * as T from '@/constants/types' import {useAllChannelMetas} from '@/teams/common/channel-hooks' import {useFeaturedBot} from '@/util/featured-bots' +import type {RPCError} from '@/util/errors' const RestrictedItem = '---RESTRICTED---' +const uiParticipantsToParticipantInfo = ( + uiParticipants: ReadonlyArray +): T.Chat.ParticipantInfo => { + const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} + uiParticipants.forEach(part => { + const {assertion, contactName, inConvName} = part + participantInfo.all.push(assertion) + if (inConvName) { + participantInfo.name.push(assertion) + } + if (contactName) { + participantInfo.contactName.set(assertion, contactName) + } + }) + return participantInfo +} + +export const useRefreshBotMembershipOnSuccess = ( + conversationIDKey: T.Chat.ConversationIDKey, + teamID: T.Teams.TeamID, + waitingKey: string, + error: RPCError | undefined, + shouldRefreshMembership: boolean, + onSuccess: () => void +) => { + const waiting = C.Waiting.useAnyWaiting(waitingKey) + const wasWaitingRef = React.useRef(waiting) + const getMembers = Teams.useTeamsState(s => s.dispatch.getMembers) + const previewConversationByID = C.useRPC(T.RPCChat.localPreviewConversationByIDLocalRpcPromise) + const setParticipants = Chat.useChatContext(s => s.dispatch.setParticipants) + + React.useEffect(() => { + if (!waiting && wasWaitingRef.current && !error) { + if (!shouldRefreshMembership) { + onSuccess() + } else { + previewConversationByID( + [{convID: T.Chat.keyToConversationID(conversationIDKey)}], + preview => { + setParticipants(uiParticipantsToParticipantInfo(preview.conv.participants ?? [])) + if (teamID) { + C.ignorePromise(getMembers(teamID)) + } + onSuccess() + }, + () => { + if (teamID) { + C.ignorePromise(getMembers(teamID)) + } + onSuccess() + } + ) + } + } + wasWaitingRef.current = waiting + }, [ + conversationIDKey, + error, + getMembers, + onSuccess, + previewConversationByID, + setParticipants, + shouldRefreshMembership, + teamID, + waiting, + ]) +} + export const useBotConversationIDKey = (inConvIDKey?: T.Chat.ConversationIDKey, teamID?: T.Teams.TeamID) => { const cleanInConvIDKey = T.Chat.isValidConversationIDKey(inConvIDKey ?? '') ? inConvIDKey : undefined const [conversationIDKey, setConversationIDKey] = React.useState(cleanInConvIDKey) @@ -126,10 +195,12 @@ const InstallBotPopup = (props: Props) => { const {channelMetas} = useAllChannelMetas(teamID) const error = C.Waiting.useAnyErrors([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) + const mutationError = C.Waiting.useAnyErrors(C.waitingKeyChatBotAdd) // dispatch const clearModals = C.Router2.clearModals const navigateUp = C.Router2.navigateUp const addBotMember = Chat.useChatContext(s => s.dispatch.addBotMember) + const [pendingMutation, setPendingMutation] = React.useState<'add' | 'edit' | undefined>() const onLearn = () => { openURL('https://book.keybase.io/docs/chat/restricted-bots') } @@ -137,6 +208,7 @@ const InstallBotPopup = (props: Props) => { if (!conversationIDKey) { return } + setPendingMutation('add') addBotMember(botUsername, installWithCommands, installWithMentions, installWithRestrict, installInConvs) } const editBotSettings = Chat.useChatContext(s => s.dispatch.editBotSettings) @@ -144,6 +216,7 @@ const InstallBotPopup = (props: Props) => { if (!conversationIDKey) { return } + setPendingMutation('edit') editBotSettings(botUsername, installWithCommands, installWithMentions, installInConvs) } const navigateAppend = C.Router2.navigateAppend @@ -172,6 +245,17 @@ const InstallBotPopup = (props: Props) => { } } }, [refreshBotRoleInConv, refreshBotSettings, conversationIDKey, inTeam, botUsername]) + useRefreshBotMembershipOnSuccess( + conversationIDKey, + teamID, + C.waitingKeyChatBotAdd, + mutationError, + pendingMutation === 'add', + () => { + setPendingMutation(undefined) + clearModals() + } + ) const noCommands = !commands?.commands const dispatchClearWaiting = C.Waiting.useDispatchClearWaiting() diff --git a/shared/constants/init/shared.tsx b/shared/constants/init/shared.tsx index 8143d85123cb..b0a4eaf339e2 100644 --- a/shared/constants/init/shared.tsx +++ b/shared/constants/init/shared.tsx @@ -178,7 +178,6 @@ export const initSharedSubscriptions = () => { chatInboxRefresh: reason => storeRegistry.getState('chat').dispatch.inboxRefresh(reason), chatMetasReceived: metas => storeRegistry.getState('chat').dispatch.metasReceived(metas), chatUnboxRows: (convIDs, force) => storeRegistry.getState('chat').dispatch.unboxRows(convIDs, force), - teamsGetMembers: async teamID => storeRegistry.getState('teams').dispatch.getMembers(teamID), usersGetBio: username => storeRegistry.getState('users').dispatch.getBio(username), }) _sharedUnsubs.push( diff --git a/shared/stores/convostate.tsx b/shared/stores/convostate.tsx index 26c065014ebf..847ab8631d0a 100644 --- a/shared/stores/convostate.tsx +++ b/shared/stores/convostate.tsx @@ -14,7 +14,6 @@ import { getModalStack, navToThread, setChatRootParams, - type PreviewConversationParams, } from '@/constants/router' import {isIOS} from '@/constants/platform' import {updateImmer} from '@/constants/utils' @@ -257,7 +256,6 @@ export interface ConvoState extends ConvoStore { chatInboxRefresh: (reason: RefreshReason) => void chatMetasReceived: (metas: ReadonlyArray) => void chatUnboxRows: (convIDs: ReadonlyArray, force: boolean) => void - teamsGetMembers: (teamID: T.RPCGen.TeamID) => Promise usersGetBio: (username: string) => void } dismissBottomBanner: () => void @@ -437,9 +435,6 @@ const stubDefer: ConvoState['dispatch']['defer'] = { chatUnboxRows: () => { throw new Error('convostate defer not initialized') }, - teamsGetMembers: () => { - throw new Error('convostate defer not initialized') - }, usersGetBio: () => { throw new Error('convostate defer not initialized') }, @@ -522,10 +517,6 @@ const createSlice = return {devicename: s.deviceName, username: s.username} } - const closeBotModal = () => { - clearModals() - } - const downloadAttachment = async (downloadToCache: boolean, ordinal: T.Chat.Ordinal) => { const messageID = get().messageMap.get(ordinal)?.id if (!messageID) return false @@ -1458,17 +1449,6 @@ const createSlice = return participantInfo } - const reloadBotMembershipState = async () => { - const {meta} = get() - const preview = await T.RPCChat.localPreviewConversationByIDLocalRpcPromise({ - convID: get().getConvID(), - }) - get().dispatch.setParticipants(uiParticipantsToParticipantInfo(preview.conv.participants ?? [])) - if (meta.teamname) { - await get().dispatch.defer.teamsGetMembers(meta.teamID) - } - } - const dispatch: ConvoState['dispatch'] = { addBotMember: (username, allowCommands, allowMentions, restricted, convs) => { const f = async () => { @@ -1482,8 +1462,6 @@ const createSlice = }, Strings.waitingKeyChatBotAdd ) - await reloadBotMembershipState() - closeBotModal() } catch (error) { if (error instanceof RPCError) { logger.info('addBotMember: failed to add bot member: ' + error.message) @@ -1709,7 +1687,6 @@ const createSlice = } return } - closeBotModal() } ignorePromise(f()) }, @@ -2800,8 +2777,6 @@ const createSlice = {convID, username}, Strings.waitingKeyChatBotRemove ) - await reloadBotMembershipState() - closeBotModal() } catch (error) { if (error instanceof RPCError) { logger.info('removeBotMember: failed to remove bot member: ' + error.message) @@ -2868,17 +2843,6 @@ const createSlice = } } - const ensureSelectedTeamLoaded = () => { - const selectedConversation = Common.getSelectedConversation() - const {meta, isMetaGood} = getConvoState(selectedConversation) - if (isMetaGood()) { - const {teamID, teamname} = meta - if (teamname) { - ignorePromise(get().dispatch.defer.teamsGetMembers(teamID)) - } - } - } - ensureSelectedTeamLoaded() const participantInfo = get().participants const force = !get().isMetaGood() || participantInfo.all.length === 0 get().dispatch.defer.chatUnboxRows([conversationIDKey], force) From 0300b4e816f7041b52061bb69ccd9b66c3dd094e Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:22:40 -0400 Subject: [PATCH 05/24] WIP --- shared/constants/init/shared.tsx | 1 - shared/stores/convostate.tsx | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/shared/constants/init/shared.tsx b/shared/constants/init/shared.tsx index b0a4eaf339e2..4b8403ff8204 100644 --- a/shared/constants/init/shared.tsx +++ b/shared/constants/init/shared.tsx @@ -178,7 +178,6 @@ export const initSharedSubscriptions = () => { chatInboxRefresh: reason => storeRegistry.getState('chat').dispatch.inboxRefresh(reason), chatMetasReceived: metas => storeRegistry.getState('chat').dispatch.metasReceived(metas), chatUnboxRows: (convIDs, force) => storeRegistry.getState('chat').dispatch.unboxRows(convIDs, force), - usersGetBio: username => storeRegistry.getState('users').dispatch.getBio(username), }) _sharedUnsubs.push( useConfigState.subscribe((s, old) => { diff --git a/shared/stores/convostate.tsx b/shared/stores/convostate.tsx index 847ab8631d0a..b9bb91f346c2 100644 --- a/shared/stores/convostate.tsx +++ b/shared/stores/convostate.tsx @@ -51,6 +51,7 @@ import * as Strings from '@/constants/strings' import {useConfigState} from '@/stores/config' import {useCurrentUserState} from '@/stores/current-user' +import {useUsersState} from '@/stores/users' import {getUsernameToShow} from '@/chat/conversation/messages/separator-utils' import type {RefreshReason} from '@/stores/chat' @@ -256,7 +257,6 @@ export interface ConvoState extends ConvoStore { chatInboxRefresh: (reason: RefreshReason) => void chatMetasReceived: (metas: ReadonlyArray) => void chatUnboxRows: (convIDs: ReadonlyArray, force: boolean) => void - usersGetBio: (username: string) => void } dismissBottomBanner: () => void dismissBlockButtons: (teamID: T.RPCGen.TeamID) => void @@ -435,9 +435,6 @@ const stubDefer: ConvoState['dispatch']['defer'] = { chatUnboxRows: () => { throw new Error('convostate defer not initialized') }, - usersGetBio: () => { - throw new Error('convostate defer not initialized') - }, } let convoDeferImpl: ConvoState['dispatch']['defer'] | undefined = __DEV__ @@ -2839,7 +2836,7 @@ const createSlice = return } - get().dispatch.defer.usersGetBio(username) + useUsersState.getState().dispatch.getBio(username) } } From 3bf2ea6f9ce7e52011b578f5c05ef45acc9cd954 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Wed, 15 Apr 2026 15:24:28 -0400 Subject: [PATCH 06/24] WIP --- shared/chat/conversation/bot/confirm.tsx | 2 +- shared/stores/convostate.tsx | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index d4b0194a559b..86ee580604c6 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' import * as Chat from '@/stores/chat' -import type * as T from '@/constants/types' +import * as T from '@/constants/types' import {useBotConversationIDKey, useRefreshBotMembershipOnSuccess} from './install' type Props = { diff --git a/shared/stores/convostate.tsx b/shared/stores/convostate.tsx index b9bb91f346c2..b22d30752c1b 100644 --- a/shared/stores/convostate.tsx +++ b/shared/stores/convostate.tsx @@ -1429,23 +1429,6 @@ const createSlice = ignorePromise(f()) } - const uiParticipantsToParticipantInfo = ( - uiParticipants: ReadonlyArray - ): T.Chat.ParticipantInfo => { - const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} - uiParticipants.forEach(part => { - const {assertion, contactName, inConvName} = part - participantInfo.all.push(assertion) - if (inConvName) { - participantInfo.name.push(assertion) - } - if (contactName) { - participantInfo.contactName.set(assertion, contactName) - } - }) - return participantInfo - } - const dispatch: ConvoState['dispatch'] = { addBotMember: (username, allowCommands, allowMentions, restricted, convs) => { const f = async () => { From 34352d70acba94e85443d10841d92a1c1e51e8da Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Wed, 15 Apr 2026 15:24:55 -0400 Subject: [PATCH 07/24] WIP --- shared/teams/team-members.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shared/teams/team-members.tsx b/shared/teams/team-members.tsx index 7c34deb75640..53a2b5cce659 100644 --- a/shared/teams/team-members.tsx +++ b/shared/teams/team-members.tsx @@ -4,7 +4,7 @@ import {useTeamsState} from '@/stores/teams' import * as React from 'react' export const useLoadTeamMembers = (teamID: T.Teams.TeamID, enabled = true) => { - const lastRequestedTeamIDRef = React.useRef() + const lastRequestedTeamIDRef = React.useRef(undefined) const getMembers = useTeamsState(s => s.dispatch.getMembers) const missing = useTeamsState(s => enabled && !!teamID && !s.teamIDToMembers.has(teamID)) @@ -27,5 +27,4 @@ export const useLoadTeamMembers = (teamID: T.Teams.TeamID, enabled = true) => { }, [enabled, getMembers, missing, teamID]) } -export const useTeamMembers = (teamID: T.Teams.TeamID) => - useTeamsState(s => s.teamIDToMembers.get(teamID)) +export const useTeamMembers = (teamID: T.Teams.TeamID) => useTeamsState(s => s.teamIDToMembers.get(teamID)) From 3b98574b97cd251ed1b65833f8734ae4cc038269 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:26:57 -0400 Subject: [PATCH 08/24] WIP --- shared/chat/conversation/bot/install.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index a8c2743d0af1..ff6ff170ea79 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -32,7 +32,7 @@ const uiParticipantsToParticipantInfo = ( } export const useRefreshBotMembershipOnSuccess = ( - conversationIDKey: T.Chat.ConversationIDKey, + conversationIDKey: T.Chat.ConversationIDKey | undefined, teamID: T.Teams.TeamID, waitingKey: string, error: RPCError | undefined, @@ -49,6 +49,8 @@ export const useRefreshBotMembershipOnSuccess = ( if (!waiting && wasWaitingRef.current && !error) { if (!shouldRefreshMembership) { onSuccess() + } else if (!conversationIDKey) { + onSuccess() } else { previewConversationByID( [{convID: T.Chat.keyToConversationID(conversationIDKey)}], From 9f8295eea2107cad6a1dbed22060e1b2745fb515 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:36:54 -0400 Subject: [PATCH 09/24] WIP --- shared/chat/conversation/bot/confirm.tsx | 4 ++-- shared/chat/conversation/bot/install.tsx | 17 ++++++++++------- shared/stores/teams.tsx | 11 ++++------- shared/teams/team-members.tsx | 22 +++++++++++++++------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index 86ee580604c6..c4e0c7191ba3 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -10,7 +10,7 @@ type Props = { conversationIDKey?: T.Chat.ConversationIDKey } -const ConfirmBotRemoveImpl = (props: {botUsername: string; teamID: T.Teams.TeamID}) => { +const ConfirmBotRemoveImpl = (props: {botUsername: string; teamID?: T.Teams.TeamID}) => { const {botUsername, teamID} = props const clearModals = C.Router2.clearModals const error = C.Waiting.useAnyErrors(C.waitingKeyChatBotRemove) @@ -47,7 +47,7 @@ const ConfirmBotRemove = (props: Props) => { const conversationIDKey = useBotConversationIDKey(props.conversationIDKey, teamID) return conversationIDKey ? ( - + ) : null } diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index ff6ff170ea79..2afd294eecb0 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -33,7 +33,7 @@ const uiParticipantsToParticipantInfo = ( export const useRefreshBotMembershipOnSuccess = ( conversationIDKey: T.Chat.ConversationIDKey | undefined, - teamID: T.Teams.TeamID, + teamID: T.Teams.TeamID | undefined, waitingKey: string, error: RPCError | undefined, shouldRefreshMembership: boolean, @@ -44,6 +44,7 @@ export const useRefreshBotMembershipOnSuccess = ( const getMembers = Teams.useTeamsState(s => s.dispatch.getMembers) const previewConversationByID = C.useRPC(T.RPCChat.localPreviewConversationByIDLocalRpcPromise) const setParticipants = Chat.useChatContext(s => s.dispatch.setParticipants) + const teamIDToRefresh = teamID && teamID !== T.Teams.noTeamID ? teamID : undefined React.useEffect(() => { if (!waiting && wasWaitingRef.current && !error) { @@ -56,14 +57,14 @@ export const useRefreshBotMembershipOnSuccess = ( [{convID: T.Chat.keyToConversationID(conversationIDKey)}], preview => { setParticipants(uiParticipantsToParticipantInfo(preview.conv.participants ?? [])) - if (teamID) { - C.ignorePromise(getMembers(teamID)) + if (teamIDToRefresh) { + C.ignorePromise(getMembers(teamIDToRefresh)) } onSuccess() }, () => { - if (teamID) { - C.ignorePromise(getMembers(teamID)) + if (teamIDToRefresh) { + C.ignorePromise(getMembers(teamIDToRefresh)) } onSuccess() } @@ -79,7 +80,7 @@ export const useRefreshBotMembershipOnSuccess = ( previewConversationByID, setParticipants, shouldRefreshMembership, - teamID, + teamIDToRefresh, waiting, ]) } @@ -190,8 +191,10 @@ const InstallBotPopup = (props: Props) => { const settings = Chat.useChatContext(s => s.botSettings.get(botUsername) ?? undefined) let teamname: string | undefined let teamID: T.Teams.TeamID = T.Teams.noTeamID + let refreshTeamID: T.Teams.TeamID | undefined if (meta.teamname) { teamID = meta.teamID + refreshTeamID = meta.teamID teamname = meta.teamname } @@ -249,7 +252,7 @@ const InstallBotPopup = (props: Props) => { }, [refreshBotRoleInConv, refreshBotSettings, conversationIDKey, inTeam, botUsername]) useRefreshBotMembershipOnSuccess( conversationIDKey, - teamID, + refreshTeamID, C.waitingKeyChatBotAdd, mutationError, pendingMutation === 'add', diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 632de37b8e24..6bef9bd2a039 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -1922,16 +1922,13 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { break } case 'chat.1.NotifyChat.ChatSetTeamRetention': { - const first = action.payload.params.convs?.[0] - if (!first) { - logger.warn('Got ChatSetTeamRetention with no convs; aborting. Local copy may be out of date') - break - } - const teamRetentionPolicy = first.teamRetention + const {convs, teamID} = action.payload.params + const first = convs?.[0] + const teamRetentionPolicy = first?.teamRetention ? Util.serviceRetentionPolicyToRetentionPolicy(first.teamRetention) : Util.makeRetentionPolicy() set(s => { - s.teamIDToRetentionPolicy.set(first.tlfID, teamRetentionPolicy) + s.teamIDToRetentionPolicy.set(teamID, teamRetentionPolicy) }) break } diff --git a/shared/teams/team-members.tsx b/shared/teams/team-members.tsx index 53a2b5cce659..0841fe46906e 100644 --- a/shared/teams/team-members.tsx +++ b/shared/teams/team-members.tsx @@ -6,25 +6,33 @@ import * as React from 'react' export const useLoadTeamMembers = (teamID: T.Teams.TeamID, enabled = true) => { const lastRequestedTeamIDRef = React.useRef(undefined) const getMembers = useTeamsState(s => s.dispatch.getMembers) - const missing = useTeamsState(s => enabled && !!teamID && !s.teamIDToMembers.has(teamID)) + const loadableTeamID = + teamID && teamID !== T.Teams.noTeamID && teamID !== T.Teams.newTeamWizardTeamID ? teamID : undefined + const missing = useTeamsState(s => enabled && !!loadableTeamID && !s.teamIDToMembers.has(loadableTeamID)) React.useEffect(() => { - if (!enabled || !teamID) { + if (!enabled || !loadableTeamID) { lastRequestedTeamIDRef.current = undefined return } if (!missing) { - if (lastRequestedTeamIDRef.current === teamID) { + if (lastRequestedTeamIDRef.current === loadableTeamID) { lastRequestedTeamIDRef.current = undefined } return } - if (lastRequestedTeamIDRef.current === teamID) { + if (lastRequestedTeamIDRef.current === loadableTeamID) { return } - lastRequestedTeamIDRef.current = teamID - C.ignorePromise(getMembers(teamID)) - }, [enabled, getMembers, missing, teamID]) + lastRequestedTeamIDRef.current = loadableTeamID + C.ignorePromise( + getMembers(loadableTeamID).finally(() => { + if (lastRequestedTeamIDRef.current === loadableTeamID) { + lastRequestedTeamIDRef.current = undefined + } + }) + ) + }, [enabled, getMembers, loadableTeamID, missing]) } export const useTeamMembers = (teamID: T.Teams.TeamID) => useTeamsState(s => s.teamIDToMembers.get(teamID)) From 07826c183efd6e636edce70b5e338eed1611da63 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:48:29 -0400 Subject: [PATCH 10/24] WIP --- shared/chat/conversation/bot/install.tsx | 2 +- shared/stores/teams.tsx | 68 ++++++++++++++++-------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 2afd294eecb0..1da0c15edefc 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -231,7 +231,7 @@ const InstallBotPopup = (props: Props) => { } navigateAppend({ name: 'chatConfirmRemoveBot', - params: {botUsername, conversationIDKey}, + params: {botUsername, conversationIDKey, teamID: refreshTeamID}, }) } const onFeedback = () => { diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 6bef9bd2a039..c44395f5646d 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -189,6 +189,8 @@ export const newTeamWizardEmptyState = { export const emptyErrorInEditMember = {error: '', teamID: T.Teams.noTeamID, username: ''} +const inflightMemberLoads = new Map>() + export const initialCanUserPerform = Object.freeze({ changeOpenTeam: false, changeTarsDisabled: false, @@ -1451,26 +1453,39 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { ignorePromise(f()) }, getMembers: async (teamID: T.Teams.TeamID) => { - try { - const res = await T.RPCGen.teamsTeamGetMembersByIDRpcPromise({ - id: teamID, - }) - const members = rpcDetailsToMemberInfos(res ?? []) - set(s => { - s.teamIDToMembers.set(teamID, members) - }) - useUsersState.getState().dispatch.updates( - [...members.values()].map(m => ({ - info: {fullname: m.fullName}, - name: m.username, - })) - ) - } catch (error) { - if (error instanceof RPCError) { - logger.error(`Error updating members for ${teamID}: ${error.desc}`) - } + if (!teamID || teamID === T.Teams.noTeamID || teamID === T.Teams.newTeamWizardTeamID) { + logger.warn(`bail on invalid team ID ${teamID}`) + return } - return + const inflight = inflightMemberLoads.get(teamID) + if (inflight) { + return inflight + } + const promise = (async () => { + try { + const res = await T.RPCGen.teamsTeamGetMembersByIDRpcPromise({ + id: teamID, + }) + const members = rpcDetailsToMemberInfos(res ?? []) + set(s => { + s.teamIDToMembers.set(teamID, members) + }) + useUsersState.getState().dispatch.updates( + [...members.values()].map(m => ({ + info: {fullname: m.fullName}, + name: m.username, + })) + ) + } catch (error) { + if (error instanceof RPCError) { + logger.error(`Error updating members for ${teamID}: ${error.desc}`) + } + } finally { + inflightMemberLoads.delete(teamID) + } + })() + inflightMemberLoads.set(teamID, promise) + return promise }, getTeamRetentionPolicy: teamID => { const f = async () => { @@ -1924,11 +1939,18 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { case 'chat.1.NotifyChat.ChatSetTeamRetention': { const {convs, teamID} = action.payload.params const first = convs?.[0] - const teamRetentionPolicy = first?.teamRetention - ? Util.serviceRetentionPolicyToRetentionPolicy(first.teamRetention) - : Util.makeRetentionPolicy() + if (!first?.teamRetention) { + logger.warn( + `Got ChatSetTeamRetention with incomplete data for ${teamID}; refetching team retention policy` + ) + get().dispatch.getTeamRetentionPolicy(teamID) + break + } set(s => { - s.teamIDToRetentionPolicy.set(teamID, teamRetentionPolicy) + s.teamIDToRetentionPolicy.set( + teamID, + Util.serviceRetentionPolicyToRetentionPolicy(first.teamRetention) + ) }) break } From eb6cc23ae9cbe7c5723196c1b9a3fb477549c741 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Wed, 15 Apr 2026 15:49:38 -0400 Subject: [PATCH 11/24] WIP --- shared/chat/conversation/bot/confirm.tsx | 2 +- shared/teams/team-members.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index c4e0c7191ba3..b4cd6f52b650 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -1,7 +1,7 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' import * as Chat from '@/stores/chat' -import * as T from '@/constants/types' +import type * as T from '@/constants/types' import {useBotConversationIDKey, useRefreshBotMembershipOnSuccess} from './install' type Props = { diff --git a/shared/teams/team-members.tsx b/shared/teams/team-members.tsx index 0841fe46906e..ca148d44078e 100644 --- a/shared/teams/team-members.tsx +++ b/shared/teams/team-members.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import type * as T from '@/constants/types' +import * as T from '@/constants/types' import {useTeamsState} from '@/stores/teams' import * as React from 'react' From 10787b618c28d2c7efa63b67fc4489b368f8e487 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:53:34 -0400 Subject: [PATCH 12/24] WIP --- shared/chat/conversation/bot/install.tsx | 4 ++-- shared/stores/teams.tsx | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 1da0c15edefc..396aa69e5526 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -58,13 +58,13 @@ export const useRefreshBotMembershipOnSuccess = ( preview => { setParticipants(uiParticipantsToParticipantInfo(preview.conv.participants ?? [])) if (teamIDToRefresh) { - C.ignorePromise(getMembers(teamIDToRefresh)) + C.ignorePromise(getMembers(teamIDToRefresh, true)) } onSuccess() }, () => { if (teamIDToRefresh) { - C.ignorePromise(getMembers(teamIDToRefresh)) + C.ignorePromise(getMembers(teamIDToRefresh, true)) } onSuccess() } diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index c44395f5646d..1bff1912a518 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -190,6 +190,7 @@ export const newTeamWizardEmptyState = { export const emptyErrorInEditMember = {error: '', teamID: T.Teams.noTeamID, username: ''} const inflightMemberLoads = new Map>() +const queuedMemberReloads = new Set() export const initialCanUserPerform = Object.freeze({ changeOpenTeam: false, @@ -872,7 +873,7 @@ export type State = Store & { finishNewTeamWizard: () => void finishedAddMembersWizard: () => void getActivityForTeams: () => void - getMembers: (teamID: T.Teams.TeamID) => Promise + getMembers: (teamID: T.Teams.TeamID, forceReload?: boolean) => Promise getTeamRetentionPolicy: (teamID: T.Teams.TeamID) => void getTeams: (subscribe?: boolean, forceReload?: boolean) => void ignoreRequest: (teamID: T.Teams.TeamID, teamname: string, username: string) => void @@ -1452,14 +1453,23 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { } ignorePromise(f()) }, - getMembers: async (teamID: T.Teams.TeamID) => { + getMembers: async (teamID: T.Teams.TeamID, forceReload = false) => { if (!teamID || teamID === T.Teams.noTeamID || teamID === T.Teams.newTeamWizardTeamID) { logger.warn(`bail on invalid team ID ${teamID}`) return } const inflight = inflightMemberLoads.get(teamID) if (inflight) { - return inflight + if (!forceReload) { + return inflight + } + queuedMemberReloads.add(teamID) + return inflight.finally(() => { + if (!queuedMemberReloads.delete(teamID)) { + return + } + return get().dispatch.getMembers(teamID) + }) } const promise = (async () => { try { From d96c66c44dbc1a381cb15ae5e3ca1913064ffb1e Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:54:55 -0400 Subject: [PATCH 13/24] WIP --- shared/stores/teams.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 1bff1912a518..0039c3ab38ac 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -1464,12 +1464,11 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { return inflight } queuedMemberReloads.add(teamID) - return inflight.finally(() => { - if (!queuedMemberReloads.delete(teamID)) { - return - } - return get().dispatch.getMembers(teamID) - }) + await inflight + if (!queuedMemberReloads.delete(teamID)) { + return + } + return get().dispatch.getMembers(teamID) } const promise = (async () => { try { From b675766da3238c7f5866e92f2c2d92e37bb3aa53 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 15:58:50 -0400 Subject: [PATCH 14/24] WIP --- shared/chat/conversation/bot/confirm.tsx | 1 + shared/chat/conversation/bot/install.tsx | 13 ++++++++++ shared/stores/teams.tsx | 33 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index b4cd6f52b650..85ecb3d7a225 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -28,6 +28,7 @@ const ConfirmBotRemoveImpl = (props: {botUsername: string; teamID?: T.Teams.Team C.waitingKeyChatBotRemove, error, true, + {username: botUsername}, clearModals ) return ( diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 396aa69e5526..5c72aa7eb382 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -37,11 +37,13 @@ export const useRefreshBotMembershipOnSuccess = ( waitingKey: string, error: RPCError | undefined, shouldRefreshMembership: boolean, + updatedBotMember: {role?: 'bot' | 'restrictedbot'; username: string} | undefined, onSuccess: () => void ) => { const waiting = C.Waiting.useAnyWaiting(waitingKey) const wasWaitingRef = React.useRef(waiting) const getMembers = Teams.useTeamsState(s => s.dispatch.getMembers) + const updateCachedBotMember = Teams.useTeamsState(s => s.dispatch.updateCachedBotMember) const previewConversationByID = C.useRPC(T.RPCChat.localPreviewConversationByIDLocalRpcPromise) const setParticipants = Chat.useChatContext(s => s.dispatch.setParticipants) const teamIDToRefresh = teamID && teamID !== T.Teams.noTeamID ? teamID : undefined @@ -57,12 +59,18 @@ export const useRefreshBotMembershipOnSuccess = ( [{convID: T.Chat.keyToConversationID(conversationIDKey)}], preview => { setParticipants(uiParticipantsToParticipantInfo(preview.conv.participants ?? [])) + if (teamIDToRefresh && updatedBotMember) { + updateCachedBotMember(teamIDToRefresh, updatedBotMember.username, updatedBotMember.role) + } if (teamIDToRefresh) { C.ignorePromise(getMembers(teamIDToRefresh, true)) } onSuccess() }, () => { + if (teamIDToRefresh && updatedBotMember) { + updateCachedBotMember(teamIDToRefresh, updatedBotMember.username, updatedBotMember.role) + } if (teamIDToRefresh) { C.ignorePromise(getMembers(teamIDToRefresh, true)) } @@ -81,6 +89,8 @@ export const useRefreshBotMembershipOnSuccess = ( setParticipants, shouldRefreshMembership, teamIDToRefresh, + updateCachedBotMember, + updatedBotMember, waiting, ]) } @@ -256,6 +266,9 @@ const InstallBotPopup = (props: Props) => { C.waitingKeyChatBotAdd, mutationError, pendingMutation === 'add', + pendingMutation === 'add' + ? {role: installWithRestrict ? 'restrictedbot' : 'bot', username: botUsername} + : undefined, () => { setPendingMutation(undefined) clearModals() diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 0039c3ab38ac..707caed6256a 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -979,6 +979,11 @@ export type State = Store & { conversationIDKey: T.Chat.ConversationIDKey, newChannelName: string ) => Promise + updateCachedBotMember: ( + teamID: T.Teams.TeamID, + username: string, + role?: 'bot' | 'restrictedbot' + ) => void updateTopic: ( teamID: T.Teams.TeamID, conversationIDKey: T.Chat.ConversationIDKey, @@ -2560,6 +2565,34 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { await T.RPCChat.localPostMetadataRpcPromise(param, S.waitingKeyTeamsUpdateChannelName(teamID)) } catch {} }, + updateCachedBotMember: (teamID, username, role) => { + if (!teamID || teamID === T.Teams.noTeamID || teamID === T.Teams.newTeamWizardTeamID) { + return + } + set(s => { + const infoFromUsers = useUsersState.getState().infoMap.get(username) + const updateMembers = (members?: Map) => { + if (!members) { + return + } + if (!role) { + members.delete(username) + return + } + const existing = members.get(username) + members.set(username, { + fullName: existing?.fullName ?? infoFromUsers?.fullname ?? '', + joinTime: existing?.joinTime, + needsPUK: existing?.needsPUK ?? false, + status: existing?.status ?? 'active', + type: role, + username, + }) + } + updateMembers(s.teamIDToMembers.get(teamID)) + updateMembers(s.teamDetails.get(teamID)?.members) + }) + }, updateTopic: async (teamID, conversationIDKey, newTopic) => { const param = { conversationID: T.Chat.keyToConversationID(conversationIDKey), From adfdf25e0c4eac56ed948956287a516f25fabefd Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 16:00:49 -0400 Subject: [PATCH 15/24] WIP --- shared/chat/conversation/bot/install.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 5c72aa7eb382..73d1b7ac02cb 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -47,6 +47,19 @@ export const useRefreshBotMembershipOnSuccess = ( const previewConversationByID = C.useRPC(T.RPCChat.localPreviewConversationByIDLocalRpcPromise) const setParticipants = Chat.useChatContext(s => s.dispatch.setParticipants) const teamIDToRefresh = teamID && teamID !== T.Teams.noTeamID ? teamID : undefined + const refreshTeamMembers = React.useEffectEvent(() => { + if (!teamIDToRefresh) { + return + } + C.ignorePromise( + (async () => { + if (updatedBotMember) { + await C.timeoutPromise(1500) + } + await getMembers(teamIDToRefresh, true) + })() + ) + }) React.useEffect(() => { if (!waiting && wasWaitingRef.current && !error) { @@ -62,18 +75,14 @@ export const useRefreshBotMembershipOnSuccess = ( if (teamIDToRefresh && updatedBotMember) { updateCachedBotMember(teamIDToRefresh, updatedBotMember.username, updatedBotMember.role) } - if (teamIDToRefresh) { - C.ignorePromise(getMembers(teamIDToRefresh, true)) - } + refreshTeamMembers() onSuccess() }, () => { if (teamIDToRefresh && updatedBotMember) { updateCachedBotMember(teamIDToRefresh, updatedBotMember.username, updatedBotMember.role) } - if (teamIDToRefresh) { - C.ignorePromise(getMembers(teamIDToRefresh, true)) - } + refreshTeamMembers() onSuccess() } ) @@ -86,6 +95,7 @@ export const useRefreshBotMembershipOnSuccess = ( getMembers, onSuccess, previewConversationByID, + refreshTeamMembers, setParticipants, shouldRefreshMembership, teamIDToRefresh, From b30fc122dcbdf6ff428712c607d594c9c500bfc9 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 16:04:25 -0400 Subject: [PATCH 16/24] WIP --- shared/chat/conversation/bot/install.tsx | 18 ----------- shared/stores/teams.tsx | 41 +++++++++++++----------- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 73d1b7ac02cb..7b8c783aa3c0 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -42,24 +42,10 @@ export const useRefreshBotMembershipOnSuccess = ( ) => { const waiting = C.Waiting.useAnyWaiting(waitingKey) const wasWaitingRef = React.useRef(waiting) - const getMembers = Teams.useTeamsState(s => s.dispatch.getMembers) const updateCachedBotMember = Teams.useTeamsState(s => s.dispatch.updateCachedBotMember) const previewConversationByID = C.useRPC(T.RPCChat.localPreviewConversationByIDLocalRpcPromise) const setParticipants = Chat.useChatContext(s => s.dispatch.setParticipants) const teamIDToRefresh = teamID && teamID !== T.Teams.noTeamID ? teamID : undefined - const refreshTeamMembers = React.useEffectEvent(() => { - if (!teamIDToRefresh) { - return - } - C.ignorePromise( - (async () => { - if (updatedBotMember) { - await C.timeoutPromise(1500) - } - await getMembers(teamIDToRefresh, true) - })() - ) - }) React.useEffect(() => { if (!waiting && wasWaitingRef.current && !error) { @@ -75,14 +61,12 @@ export const useRefreshBotMembershipOnSuccess = ( if (teamIDToRefresh && updatedBotMember) { updateCachedBotMember(teamIDToRefresh, updatedBotMember.username, updatedBotMember.role) } - refreshTeamMembers() onSuccess() }, () => { if (teamIDToRefresh && updatedBotMember) { updateCachedBotMember(teamIDToRefresh, updatedBotMember.username, updatedBotMember.role) } - refreshTeamMembers() onSuccess() } ) @@ -92,10 +76,8 @@ export const useRefreshBotMembershipOnSuccess = ( }, [ conversationIDKey, error, - getMembers, onSuccess, previewConversationByID, - refreshTeamMembers, setParticipants, shouldRefreshMembership, teamIDToRefresh, diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 707caed6256a..ba453b66c881 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -974,16 +974,16 @@ export type State = Store & { teamSeen: (teamID: T.Teams.TeamID) => void unsubscribeTeamDetails: (teamID: T.Teams.TeamID) => void unsubscribeTeamList: () => void - updateChannelName: ( - teamID: T.Teams.TeamID, - conversationIDKey: T.Chat.ConversationIDKey, - newChannelName: string - ) => Promise updateCachedBotMember: ( teamID: T.Teams.TeamID, username: string, role?: 'bot' | 'restrictedbot' ) => void + updateChannelName: ( + teamID: T.Teams.TeamID, + conversationIDKey: T.Chat.ConversationIDKey, + newChannelName: string + ) => Promise updateTopic: ( teamID: T.Teams.TeamID, conversationIDKey: T.Chat.ConversationIDKey, @@ -2506,7 +2506,7 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { navigateAppend('teamAddToTeamFromWhere') }, teamChangedByID: c => { - const {teamID, latestHiddenSeqno, latestOffchainSeqno, latestSeqno} = c + const {changes, teamID, latestHiddenSeqno, latestOffchainSeqno, latestSeqno} = c // Any of the Seqnos can be 0, which means that it was unknown at the source // at the time when this notification was generated. const version = get().teamVersion.get(teamID) @@ -2527,6 +2527,9 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { if (shouldLoad) { get().dispatch.loadTeam(teamID) } + if (changes.membershipChanged && get().teamIDToMembers.has(teamID)) { + ignorePromise(get().dispatch.getMembers(teamID, true)) + } }, teamSeen: teamID => { const f = async () => { @@ -2552,19 +2555,6 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { } }) }, - updateChannelName: async (teamID, conversationIDKey, newChannelName) => { - const param = { - channelName: newChannelName, - conversationID: T.Chat.keyToConversationID(conversationIDKey), - identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, - tlfName: getTeamNameFromID(get(), teamID) ?? '', - tlfPublic: false, - } - - try { - await T.RPCChat.localPostMetadataRpcPromise(param, S.waitingKeyTeamsUpdateChannelName(teamID)) - } catch {} - }, updateCachedBotMember: (teamID, username, role) => { if (!teamID || teamID === T.Teams.noTeamID || teamID === T.Teams.newTeamWizardTeamID) { return @@ -2593,6 +2583,19 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { updateMembers(s.teamDetails.get(teamID)?.members) }) }, + updateChannelName: async (teamID, conversationIDKey, newChannelName) => { + const param = { + channelName: newChannelName, + conversationID: T.Chat.keyToConversationID(conversationIDKey), + identifyBehavior: T.RPCGen.TLFIdentifyBehavior.chatGui, + tlfName: getTeamNameFromID(get(), teamID) ?? '', + tlfPublic: false, + } + + try { + await T.RPCChat.localPostMetadataRpcPromise(param, S.waitingKeyTeamsUpdateChannelName(teamID)) + } catch {} + }, updateTopic: async (teamID, conversationIDKey, newTopic) => { const param = { conversationID: T.Chat.keyToConversationID(conversationIDKey), From e92870338c2a9fadab0ae7a6263b8040b13d53a5 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 16:07:57 -0400 Subject: [PATCH 17/24] WIP --- shared/chat/conversation/bot/install.tsx | 33 ++++++++++++------------ 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 7b8c783aa3c0..8e3af61125ec 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -168,18 +168,17 @@ const InstallBotPopup = (props: Props) => { const [botPublicCommands, setBotPublicCommands] = React.useState() const meta = Chat.useChatContext(s => s.meta) - const commands = (() => { - const {botCommands} = meta - const commands = ( - botCommands.typ === T.RPCChat.ConversationCommandGroupsTyp.custom - ? botCommands.custom.commands || blankCommands - : blankCommands - ) - .filter(c => c.username === botUsername) - .map(c => c.name) - const convCommands = {commands, loadError: false} satisfies T.Chat.BotPublicCommands - return commands.length > 0 ? convCommands : botPublicCommands - })() + const commandsFromMeta = ( + meta.botCommands.typ === T.RPCChat.ConversationCommandGroupsTyp.custom + ? meta.botCommands.custom.commands || blankCommands + : blankCommands + ) + .filter(c => c.username === botUsername) + .map(c => c.name) + const commands = + commandsFromMeta.length > 0 + ? ({commands: commandsFromMeta, loadError: false} satisfies T.Chat.BotPublicCommands) + : botPublicCommands const featured = useFeaturedBot(botUsername) const teamRole = Chat.useChatContext(s => s.botTeamRoleMap.get(botUsername)) @@ -266,19 +265,19 @@ const InstallBotPopup = (props: Props) => { clearModals() } ) - const noCommands = !commands?.commands const dispatchClearWaiting = C.Waiting.useDispatchClearWaiting() const loadBotPublicCommands = C.useRPC(T.RPCChat.localListPublicBotCommandsLocalRpcPromise) const botPublicCommandsRequestIDRef = React.useRef(0) + React.useEffect(() => { + setBotPublicCommands(undefined) + }, [botUsername]) React.useEffect(() => { dispatchClearWaiting([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) botPublicCommandsRequestIDRef.current += 1 - if (!noCommands) { - setBotPublicCommands(undefined) + if (commandsFromMeta.length > 0) { return } - setBotPublicCommands(undefined) const requestID = botPublicCommandsRequestIDRef.current loadBotPublicCommands( [{username: botUsername}], @@ -301,7 +300,7 @@ const InstallBotPopup = (props: Props) => { botPublicCommandsRequestIDRef.current += 1 } } - }, [botUsername, dispatchClearWaiting, loadBotPublicCommands, noCommands]) + }, [botUsername, commandsFromMeta.length, dispatchClearWaiting, loadBotPublicCommands]) const restrictedButton = ( From 10250f3fea7ea0b3b9b6949d02ee4b654b8e7843 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 16:14:46 -0400 Subject: [PATCH 18/24] WIP --- shared/stores/teams.tsx | 23 +++++++++++++++++++++-- shared/teams/channel/index.tsx | 32 +++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index ba453b66c881..7846d120a743 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -191,6 +191,15 @@ export const emptyErrorInEditMember = {error: '', teamID: T.Teams.noTeamID, user const inflightMemberLoads = new Map>() const queuedMemberReloads = new Set() +let memberLoadGeneration = 0 + +const clearMemberLoadTracking = () => { + memberLoadGeneration += 1 + inflightMemberLoads.clear() + queuedMemberReloads.clear() +} + +Z.registerExternalResetter('teams-member-loads', clearMemberLoadTracking) export const initialCanUserPerform = Object.freeze({ changeOpenTeam: false, @@ -1463,6 +1472,7 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { logger.warn(`bail on invalid team ID ${teamID}`) return } + const generation = memberLoadGeneration const inflight = inflightMemberLoads.get(teamID) if (inflight) { if (!forceReload) { @@ -1470,16 +1480,23 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { } queuedMemberReloads.add(teamID) await inflight + if (generation !== memberLoadGeneration) { + return + } if (!queuedMemberReloads.delete(teamID)) { return } return get().dispatch.getMembers(teamID) } - const promise = (async () => { + let promise: Promise + promise = (async () => { try { const res = await T.RPCGen.teamsTeamGetMembersByIDRpcPromise({ id: teamID, }) + if (generation !== memberLoadGeneration) { + return + } const members = rpcDetailsToMemberInfos(res ?? []) set(s => { s.teamIDToMembers.set(teamID, members) @@ -1495,7 +1512,9 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { logger.error(`Error updating members for ${teamID}: ${error.desc}`) } } finally { - inflightMemberLoads.delete(teamID) + if (inflightMemberLoads.get(teamID) === promise) { + inflightMemberLoads.delete(teamID) + } } })() inflightMemberLoads.set(teamID, promise) diff --git a/shared/teams/channel/index.tsx b/shared/teams/channel/index.tsx index e7d668ab892f..89366f53d5f0 100644 --- a/shared/teams/channel/index.tsx +++ b/shared/teams/channel/index.tsx @@ -99,6 +99,7 @@ type Item = | {type: 'load-more'} | {type: 'header-section'} | {type: 'headerSection'} + | {type: 'membersLoading'} | {type: 'membersSection'; username: string} | {type: 'membersEmpty'} | {type: 'membersFew'} @@ -118,16 +119,25 @@ const Channel = (props: OwnProps) => { const providedTab = props.selectedTab const meta = Chat.useConvoState(conversationIDKey, s => s.meta) - const teamMembers = Teams.useTeamsState(s => s.teamIDToMembers.get(teamID) ?? emptyMapForUseSelector) + const teamMembers = Teams.useTeamsState(s => s.teamIDToMembers.get(teamID)) const {bots, participants: _participants} = Chat.useConvoState( conversationIDKey, - C.useDeep(s => Chat.getBotsAndParticipants(meta, s.participants, teamMembers, true /* sort */)) + C.useDeep(s => + Chat.getBotsAndParticipants(meta, s.participants, teamMembers ?? emptyMapForUseSelector, true /* sort */) + ) ) const yourOperations = Teams.useTeamsState(s => Teams.getCanPerformByID(s, teamID)) const isPreview = meta.membershipType === 'youArePreviewing' || meta.membershipType === 'notMember' const [selectedTab, setSelectedTab] = useTabsState(conversationIDKey, providedTab) useLoadDataForChannelPage(teamID, conversationIDKey, selectedTab, meta, _participants) - const participants = useChannelParticipants(teamID, conversationIDKey) + const channelParticipants = useChannelParticipants(teamID, conversationIDKey) + const generalMembersLoading = meta.channelname === 'general' && !teamMembers + const participants = + meta.channelname === 'general' && teamMembers + ? [...teamMembers.values()] + .filter(member => member.type !== 'bot' && member.type !== 'restrictedbot') + .map(member => member.username) + : channelParticipants // Make the actual sections (consider farming this out into another function or file) const headerSection: Section = { @@ -156,7 +166,9 @@ const Channel = (props: OwnProps) => { switch (selectedTab) { case 'members': { sections.push({ - data: participants.map(p => ({type: 'membersSection', username: p})), + data: generalMembersLoading + ? [{type: 'membersLoading'}] + : participants.map(p => ({type: 'membersSection', username: p})), renderItem: ({index, item}: {index: number; item: Item}) => item.type === 'membersSection' ? ( { firstItem={index === 0} isGeneral={meta.channelname === 'general'} /> + ) : item.type === 'membersLoading' ? ( + ) : null, - title: `Members (${participants.length})`, + title: generalMembersLoading ? 'Members' : `Members (${participants.length})`, } as const) - if (participants.length === 0) { + if (!generalMembersLoading && participants.length === 0) { sections.push({ data: [{type: 'membersEmpty'}], renderItem: () => ( @@ -195,12 +209,12 @@ const Channel = (props: OwnProps) => { break } case 'bots': { - const botsInTeamNotInConv = [...teamMembers.values()] + const botsInTeamNotInConv = [...(teamMembers ?? emptyMapForUseSelector).values()] .map(p => p.username) .filter( p => - Teams.userIsRoleInTeamWithInfo(teamMembers, p, 'restrictedbot') || - Teams.userIsRoleInTeamWithInfo(teamMembers, p, 'bot') + Teams.userIsRoleInTeamWithInfo(teamMembers ?? emptyMapForUseSelector, p, 'restrictedbot') || + Teams.userIsRoleInTeamWithInfo(teamMembers ?? emptyMapForUseSelector, p, 'bot') ) .filter(p => !bots.includes(p)) .sort((l, r) => l.localeCompare(r)) From 3500725c751b1a1c03f9b76a752bfcc79aa6e6be Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 16:30:34 -0400 Subject: [PATCH 19/24] WIP --- shared/stores/teams.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 7846d120a743..0606e168202b 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -189,18 +189,6 @@ export const newTeamWizardEmptyState = { export const emptyErrorInEditMember = {error: '', teamID: T.Teams.noTeamID, username: ''} -const inflightMemberLoads = new Map>() -const queuedMemberReloads = new Set() -let memberLoadGeneration = 0 - -const clearMemberLoadTracking = () => { - memberLoadGeneration += 1 - inflightMemberLoads.clear() - queuedMemberReloads.clear() -} - -Z.registerExternalResetter('teams-member-loads', clearMemberLoadTracking) - export const initialCanUserPerform = Object.freeze({ changeOpenTeam: false, changeTarsDisabled: false, @@ -1008,6 +996,18 @@ export type State = Store & { } export const useTeamsState = Z.createZustand('teams', (set, get) => { + let inflightMemberLoads = new Map>() + let queuedMemberReloads = new Set() + let memberLoadGeneration = 0 + const clearMemberLoadTracking = () => { + memberLoadGeneration += 1 + inflightMemberLoads = new Map() + queuedMemberReloads = new Set() + } + const resetState = () => { + clearMemberLoadTracking() + set({...initialStore, dispatch}, true) + } const dispatch: State['dispatch'] = { addMembersWizardPushMembers: members => { const f = async () => { @@ -2168,7 +2168,7 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { s.errorInEmailInvite.malformed = new Set() }) }, - resetState: Z.defaultReset, + resetState, resetTeamMetaStale: () => { set(s => { s.teamMetaStale = true From 017289cc38836f8d1999d9f67427c2d72339ca1b Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 16:32:05 -0400 Subject: [PATCH 20/24] WIP --- shared/stores/teams.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shared/stores/teams.tsx b/shared/stores/teams.tsx index 0606e168202b..517493c7c504 100644 --- a/shared/stores/teams.tsx +++ b/shared/stores/teams.tsx @@ -997,11 +997,13 @@ export type State = Store & { export const useTeamsState = Z.createZustand('teams', (set, get) => { let inflightMemberLoads = new Map>() + let inflightMemberLoadTokens = new Map() let queuedMemberReloads = new Set() let memberLoadGeneration = 0 const clearMemberLoadTracking = () => { memberLoadGeneration += 1 inflightMemberLoads = new Map() + inflightMemberLoadTokens = new Map() queuedMemberReloads = new Set() } const resetState = () => { @@ -1488,8 +1490,8 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { } return get().dispatch.getMembers(teamID) } - let promise: Promise - promise = (async () => { + const requestToken = Symbol(String(teamID)) + const promise = (async () => { try { const res = await T.RPCGen.teamsTeamGetMembersByIDRpcPromise({ id: teamID, @@ -1512,12 +1514,14 @@ export const useTeamsState = Z.createZustand('teams', (set, get) => { logger.error(`Error updating members for ${teamID}: ${error.desc}`) } } finally { - if (inflightMemberLoads.get(teamID) === promise) { + if (inflightMemberLoadTokens.get(teamID) === requestToken) { inflightMemberLoads.delete(teamID) + inflightMemberLoadTokens.delete(teamID) } } })() inflightMemberLoads.set(teamID, promise) + inflightMemberLoadTokens.set(teamID, requestToken) return promise }, getTeamRetentionPolicy: teamID => { From e44f4bf328351483ff1e2e853265a68aff8d4e92 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 17:17:35 -0400 Subject: [PATCH 21/24] WIP --- shared/chat/conversation/bot/install.tsx | 20 ++------------------ shared/constants/chat/common.tsx | 17 +++++++++++++++++ shared/stores/chat.tsx | 23 +++-------------------- shared/teams/channel/index.tsx | 8 ++++---- 4 files changed, 26 insertions(+), 42 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 8e3af61125ec..0a85ac58a037 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -1,4 +1,5 @@ import * as C from '@/constants' +import * as ChatCommon from '@/constants/chat/common' import * as Meta from '@/constants/chat/meta' import * as Chat from '@/stores/chat' import * as Kb from '@/common-adapters' @@ -14,23 +15,6 @@ import type {RPCError} from '@/util/errors' const RestrictedItem = '---RESTRICTED---' -const uiParticipantsToParticipantInfo = ( - uiParticipants: ReadonlyArray -): T.Chat.ParticipantInfo => { - const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} - uiParticipants.forEach(part => { - const {assertion, contactName, inConvName} = part - participantInfo.all.push(assertion) - if (inConvName) { - participantInfo.name.push(assertion) - } - if (contactName) { - participantInfo.contactName.set(assertion, contactName) - } - }) - return participantInfo -} - export const useRefreshBotMembershipOnSuccess = ( conversationIDKey: T.Chat.ConversationIDKey | undefined, teamID: T.Teams.TeamID | undefined, @@ -57,7 +41,7 @@ export const useRefreshBotMembershipOnSuccess = ( previewConversationByID( [{convID: T.Chat.keyToConversationID(conversationIDKey)}], preview => { - setParticipants(uiParticipantsToParticipantInfo(preview.conv.participants ?? [])) + setParticipants(ChatCommon.uiParticipantsToParticipantInfo(preview.conv.participants ?? [])) if (teamIDToRefresh && updatedBotMember) { updateCachedBotMember(teamIDToRefresh, updatedBotMember.username, updatedBotMember.role) } diff --git a/shared/constants/chat/common.tsx b/shared/constants/chat/common.tsx index c6ab33d2cd33..cb3f54b501a3 100644 --- a/shared/constants/chat/common.tsx +++ b/shared/constants/chat/common.tsx @@ -63,3 +63,20 @@ export const allMessageTypes: Set = new Set([ export const generateOutboxID = () => Uint8Array.from([...Array(8)], () => Math.floor(Math.random() * 256)) + +export const uiParticipantsToParticipantInfo = ( + uiParticipants: ReadonlyArray +): T.Chat.ParticipantInfo => { + const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} + uiParticipants.forEach(part => { + const {assertion, contactName, inConvName} = part + participantInfo.all.push(assertion) + if (inConvName) { + participantInfo.name.push(assertion) + } + if (contactName) { + participantInfo.contactName.set(assertion, contactName) + } + }) + return participantInfo +} diff --git a/shared/stores/chat.tsx b/shared/stores/chat.tsx index cfc22b086788..281932b6c51a 100644 --- a/shared/stores/chat.tsx +++ b/shared/stores/chat.tsx @@ -148,23 +148,6 @@ export const zoomImage = (width: number, height: number, maxThumbSize: number) = } } -const uiParticipantsToParticipantInfo = ( - uiParticipants: ReadonlyArray -): T.Chat.ParticipantInfo => { - const participantInfo = {all: new Array(), contactName: new Map(), name: new Array()} - uiParticipants.forEach(part => { - const {assertion, contactName, inConvName} = part - participantInfo.all.push(assertion) - if (inConvName) { - participantInfo.name.push(assertion) - } - if (contactName) { - participantInfo.contactName.set(assertion, contactName) - } - }) - return participantInfo -} - /** * Returns true if the team is big and you're a member */ @@ -320,7 +303,7 @@ export const useChatState = Z.createZustand('chat', (set, get) => { get().dispatch.metasReceived([meta]) } - const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( + const participantInfo: T.Chat.ParticipantInfo = Common.uiParticipantsToParticipantInfo( uiConv.participants ?? [] ) if (participantInfo.all.length > 0) { @@ -624,7 +607,7 @@ export const useChatState = Z.createZustand('chat', (set, get) => { if (participants) { storeRegistry .getConvoState(conversationIDKey) - .dispatch.setParticipants(uiParticipantsToParticipantInfo(participants)) + .dispatch.setParticipants(Common.uiParticipantsToParticipantInfo(participants)) } }) break @@ -907,7 +890,7 @@ export const useChatState = Z.createZustand('chat', (set, get) => { if (meta) { metas.push(meta) } - const participantInfo: T.Chat.ParticipantInfo = uiParticipantsToParticipantInfo( + const participantInfo: T.Chat.ParticipantInfo = Common.uiParticipantsToParticipantInfo( inboxUIItem.participants ?? [] ) if (participantInfo.all.length > 0) { diff --git a/shared/teams/channel/index.tsx b/shared/teams/channel/index.tsx index 89366f53d5f0..54ae16f39bbf 100644 --- a/shared/teams/channel/index.tsx +++ b/shared/teams/channel/index.tsx @@ -133,10 +133,10 @@ const Channel = (props: OwnProps) => { const channelParticipants = useChannelParticipants(teamID, conversationIDKey) const generalMembersLoading = meta.channelname === 'general' && !teamMembers const participants = - meta.channelname === 'general' && teamMembers - ? [...teamMembers.values()] - .filter(member => member.type !== 'bot' && member.type !== 'restrictedbot') - .map(member => member.username) + meta.channelname === 'general' + ? teamMembers + ? _participants + : channelParticipants : channelParticipants // Make the actual sections (consider farming this out into another function or file) From 58a3ac73dbcfef481aca349ae0da9dcb47bbbc13 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 17:41:05 -0400 Subject: [PATCH 22/24] WIP --- shared/chat/conversation/bot/install.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 0a85ac58a037..6f34f0a030f3 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -184,6 +184,7 @@ const InstallBotPopup = (props: Props) => { } const {channelMetas} = useAllChannelMetas(teamID) + const mutationWaiting = C.Waiting.useAnyWaiting([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) const error = C.Waiting.useAnyErrors([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) const mutationError = C.Waiting.useAnyErrors(C.waitingKeyChatBotAdd) // dispatch @@ -257,7 +258,9 @@ const InstallBotPopup = (props: Props) => { setBotPublicCommands(undefined) }, [botUsername]) React.useEffect(() => { - dispatchClearWaiting([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) + if (!mutationWaiting) { + dispatchClearWaiting([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) + } botPublicCommandsRequestIDRef.current += 1 if (commandsFromMeta.length > 0) { return @@ -284,7 +287,7 @@ const InstallBotPopup = (props: Props) => { botPublicCommandsRequestIDRef.current += 1 } } - }, [botUsername, commandsFromMeta.length, dispatchClearWaiting, loadBotPublicCommands]) + }, [botUsername, commandsFromMeta.length, dispatchClearWaiting, loadBotPublicCommands, mutationWaiting]) const restrictedButton = ( From e693b9b8f6b5f90785d89d5d21cf3deaf0d53082 Mon Sep 17 00:00:00 2001 From: Chris Nojima Date: Wed, 15 Apr 2026 18:02:13 -0400 Subject: [PATCH 23/24] WIP --- shared/chat/conversation/bot/install.tsx | 10 ++++++++-- shared/teams/channel/index.tsx | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 6f34f0a030f3..01fb8dec5d2f 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -199,6 +199,7 @@ const InstallBotPopup = (props: Props) => { if (!conversationIDKey) { return } + dispatchClearWaiting([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) setPendingMutation('add') addBotMember(botUsername, installWithCommands, installWithMentions, installWithRestrict, installInConvs) } @@ -207,6 +208,7 @@ const InstallBotPopup = (props: Props) => { if (!conversationIDKey) { return } + dispatchClearWaiting([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) setPendingMutation('edit') editBotSettings(botUsername, installWithCommands, installWithMentions, installInConvs) } @@ -254,13 +256,17 @@ const InstallBotPopup = (props: Props) => { const dispatchClearWaiting = C.Waiting.useDispatchClearWaiting() const loadBotPublicCommands = C.useRPC(T.RPCChat.localListPublicBotCommandsLocalRpcPromise) const botPublicCommandsRequestIDRef = React.useRef(0) + const clearedWaitingForBotRef = React.useRef() React.useEffect(() => { setBotPublicCommands(undefined) }, [botUsername]) React.useEffect(() => { - if (!mutationWaiting) { + if (!mutationWaiting && clearedWaitingForBotRef.current !== botUsername) { + clearedWaitingForBotRef.current = botUsername dispatchClearWaiting([C.waitingKeyChatBotAdd, C.waitingKeyChatBotRemove]) } + }, [botUsername, dispatchClearWaiting, mutationWaiting]) + React.useEffect(() => { botPublicCommandsRequestIDRef.current += 1 if (commandsFromMeta.length > 0) { return @@ -287,7 +293,7 @@ const InstallBotPopup = (props: Props) => { botPublicCommandsRequestIDRef.current += 1 } } - }, [botUsername, commandsFromMeta.length, dispatchClearWaiting, loadBotPublicCommands, mutationWaiting]) + }, [botUsername, commandsFromMeta.length, loadBotPublicCommands]) const restrictedButton = ( diff --git a/shared/teams/channel/index.tsx b/shared/teams/channel/index.tsx index 54ae16f39bbf..c415999ccae1 100644 --- a/shared/teams/channel/index.tsx +++ b/shared/teams/channel/index.tsx @@ -32,16 +32,29 @@ const useLoadDataForChannelPage = ( participants: ReadonlyArray ) => { const prevSelectedTabRef = React.useRef(selectedTab) + const prevParticipantsRef = React.useRef(participants) + const loadedBlockStateForConvRef = React.useRef(false) const getBlockState = useUsersState(s => s.dispatch.getBlockState) const unboxRows = Chat.useChatState(s => s.dispatch.unboxRows) useLoadTeamMembers(teamID, ['bots', 'members', 'settings'].includes(selectedTab)) React.useEffect(() => { - if (selectedTab !== prevSelectedTabRef.current && selectedTab === 'members') { + loadedBlockStateForConvRef.current = false + }, [conversationIDKey]) + React.useEffect(() => { + const participantsChanged = + participants.length !== prevParticipantsRef.current.length || + participants.some((participant, index) => participant !== prevParticipantsRef.current[index]) + if ( + selectedTab === 'members' && + (!loadedBlockStateForConvRef.current || selectedTab !== prevSelectedTabRef.current || participantsChanged) + ) { if (meta.conversationIDKey === 'EMPTY') { unboxRows([conversationIDKey]) } getBlockState(participants) + loadedBlockStateForConvRef.current = true } + prevParticipantsRef.current = participants }, [ unboxRows, getBlockState, @@ -129,7 +142,6 @@ const Channel = (props: OwnProps) => { const yourOperations = Teams.useTeamsState(s => Teams.getCanPerformByID(s, teamID)) const isPreview = meta.membershipType === 'youArePreviewing' || meta.membershipType === 'notMember' const [selectedTab, setSelectedTab] = useTabsState(conversationIDKey, providedTab) - useLoadDataForChannelPage(teamID, conversationIDKey, selectedTab, meta, _participants) const channelParticipants = useChannelParticipants(teamID, conversationIDKey) const generalMembersLoading = meta.channelname === 'general' && !teamMembers const participants = @@ -138,6 +150,7 @@ const Channel = (props: OwnProps) => { ? _participants : channelParticipants : channelParticipants + useLoadDataForChannelPage(teamID, conversationIDKey, selectedTab, meta, participants) // Make the actual sections (consider farming this out into another function or file) const headerSection: Section = { From e62b60038e719dc9cc2f7512252546337d8e9a40 Mon Sep 17 00:00:00 2001 From: chrisnojima-zoom <83838430+chrisnojima-zoom@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:41:12 -0400 Subject: [PATCH 24/24] split chat and convostate part 2 (#29152) --- shared/chat/audio/audio-recorder.native.tsx | 16 +- shared/chat/blocking/block-modal.tsx | 4 +- shared/chat/blocking/invitation-to-block.tsx | 14 +- .../attachment-fullscreen/hooks.tsx | 14 +- .../conversation/attachment-get-titles.tsx | 9 +- shared/chat/conversation/bot/confirm.tsx | 10 +- shared/chat/conversation/bot/install.tsx | 27 +- shared/chat/conversation/bot/search.tsx | 4 +- shared/chat/conversation/bottom-banner.tsx | 14 +- shared/chat/conversation/command-markdown.tsx | 4 +- shared/chat/conversation/command-status.tsx | 6 +- shared/chat/conversation/container.tsx | 3 +- shared/chat/conversation/error.tsx | 4 +- shared/chat/conversation/fwd-msg.tsx | 5 +- shared/chat/conversation/giphy/hooks.tsx | 6 +- .../conversation/header-area/index.native.tsx | 54 +- .../conversation/info-panel/add-people.tsx | 6 +- .../info-panel/add-to-channel.tsx | 48 +- .../conversation/info-panel/attachments.tsx | 17 +- shared/chat/conversation/info-panel/bot.tsx | 23 +- .../chat/conversation/info-panel/common.tsx | 6 +- .../chat/conversation/info-panel/header.tsx | 16 +- shared/chat/conversation/info-panel/index.tsx | 9 +- .../chat/conversation/info-panel/members.tsx | 13 +- shared/chat/conversation/info-panel/menu.tsx | 25 +- .../info-panel/settings/index.tsx | 16 +- .../info-panel/settings/min-writer-role.tsx | 6 +- .../info-panel/settings/notifications.tsx | 8 +- .../conversation/input-area/container.tsx | 5 +- .../input-area/location-popup.native.tsx | 64 +- .../conversation/input-area/normal/index.tsx | 27 +- .../input-area/normal/input.desktop.tsx | 17 +- .../input-area/normal/input.native.tsx | 10 +- .../normal/moremenu-popup.native.tsx | 6 +- .../normal/set-explode-popup/hooks.tsx | 5 +- .../conversation/input-area/normal/typing.tsx | 6 +- .../chat/conversation/input-area/preview.tsx | 8 +- .../input-area/suggestors/channels.tsx | 10 +- .../input-area/suggestors/commands.tsx | 11 +- .../input-area/suggestors/emoji.tsx | 4 +- .../input-area/suggestors/index.tsx | 8 +- .../input-area/suggestors/users.tsx | 3 +- shared/chat/conversation/list-area/hooks.tsx | 5 +- .../conversation/list-area/index.desktop.tsx | 28 +- .../conversation/list-area/index.native.tsx | 15 +- shared/chat/conversation/load-status.tsx | 6 +- .../messages/account-payment/container.tsx | 6 +- .../conversation/messages/attachment/file.tsx | 3 +- .../messages/attachment/image/index.tsx | 25 +- .../messages/attachment/shared.tsx | 9 +- .../messages/attachment/video/index.tsx | 33 +- .../conversation/messages/cards/make-team.tsx | 4 +- .../messages/cards/team-journey/container.tsx | 12 +- .../chat/conversation/messages/emoji-row.tsx | 7 +- .../messages/message-popup/attachment.tsx | 5 +- .../messages/message-popup/hooks.tsx | 17 +- .../messages/message-popup/index.tsx | 10 +- .../messages/message-popup/journeycard.tsx | 6 +- .../messages/message-popup/text.tsx | 5 +- .../chat/conversation/messages/pin/index.tsx | 4 +- .../conversation/messages/react-button.tsx | 4 +- .../messages/reaction-tooltip.tsx | 13 +- .../chat/conversation/messages/reset-user.tsx | 10 +- .../messages/retention-notice.tsx | 6 +- .../chat/conversation/messages/separator.tsx | 3 +- .../messages/special-bottom-message.tsx | 3 +- .../messages/special-top-message.tsx | 13 +- .../system-added-to-team/container.tsx | 6 +- .../system-change-retention/container.tsx | 4 +- .../messages/system-create-team/container.tsx | 4 +- .../system-invite-accepted/container.tsx | 4 +- .../messages/system-joined/container.tsx | 12 +- .../messages/system-left/container.tsx | 4 +- .../messages/system-new-channel/container.tsx | 8 +- .../container.tsx | 8 +- .../messages/system-profile-reset-notice.tsx | 6 +- .../system-simple-to-complex/container.tsx | 4 +- .../system-users-added-to-conv/container.tsx | 4 +- .../messages/text/coinflip/index.tsx | 6 +- .../chat/conversation/messages/text/reply.tsx | 4 +- .../text/unfurl/prompt-list/container.tsx | 4 +- .../text/unfurl/unfurl-list/image/index.tsx | 4 +- .../text/unfurl/unfurl-list/map-popup.tsx | 28 +- .../text/unfurl/unfurl-list/use-state.tsx | 6 +- .../conversation/messages/wrapper/index.tsx | 10 +- .../wrapper/long-pressable/index.native.tsx | 6 +- .../conversation/messages/wrapper/wrapper.tsx | 36 +- shared/chat/conversation/normal/container.tsx | 18 +- .../conversation/normal/index.desktop.tsx | 18 +- .../chat/conversation/normal/index.native.tsx | 8 +- shared/chat/conversation/pinned-message.tsx | 7 +- shared/chat/conversation/rekey/container.tsx | 4 +- shared/chat/conversation/reply-preview.tsx | 11 +- shared/chat/conversation/search.tsx | 20 +- shared/chat/delete-history-warning.tsx | 4 +- shared/chat/emoji-picker/container.tsx | 13 +- shared/chat/inbox-and-conversation-header.tsx | 7 +- shared/chat/inbox-and-conversation-shared.tsx | 7 +- shared/chat/inbox-search/index.tsx | 13 +- shared/chat/inbox/row/big-team-channel.tsx | 5 +- shared/chat/inbox/row/big-team-header.tsx | 6 +- shared/chat/inbox/row/small-team/index.tsx | 9 +- .../swipe-conv-actions/index.native.tsx | 3 +- .../inbox/row/teams-divider-container.tsx | 26 +- shared/chat/inbox/use-inbox-search.tsx | 14 +- shared/chat/inbox/use-inbox-state.test.ts | 21 +- shared/chat/inbox/use-inbox-state.tsx | 38 +- shared/chat/make-chat-screen.tsx | 72 ++ shared/chat/new-team-dialog-container.tsx | 4 +- shared/chat/payments/status/index.tsx | 15 +- shared/chat/pdf/index.desktop.tsx | 6 +- shared/chat/routes.tsx | 47 +- .../selectable-big-team-channel-container.tsx | 8 +- .../chat/selectable-small-team-container.tsx | 11 +- shared/chat/send-to-chat/index.tsx | 9 +- .../markdown/maybe-mention/index.tsx | 6 +- .../markdown/maybe-mention/team.tsx | 3 +- shared/common-adapters/wave-button.tsx | 10 +- shared/constants/chat/debug.tsx | 4 +- shared/constants/chat/helpers.tsx | 105 ++ shared/constants/chat/index.tsx | 0 shared/constants/init/shared.tsx | 111 +- shared/constants/router.tsx | 9 +- .../renderer/remote-event-handler.desktop.tsx | 2 +- shared/incoming-share/index.tsx | 14 +- shared/menubar/remote-proxy.desktop.tsx | 27 +- shared/stores/chat-shared.tsx | 13 + shared/stores/chat.tsx | 1118 +---------------- shared/stores/convo-registry.tsx | 27 + shared/stores/convostate.tsx | 941 +++++++++++++- shared/stores/inbox-rows.tsx | 41 +- shared/stores/tests/chat.test.ts | 12 +- shared/stores/tests/convostate.test.ts | 63 +- shared/teams/add-members-wizard/confirm.tsx | 3 +- shared/teams/channel/index.tsx | 13 +- shared/teams/channel/rows.tsx | 4 +- shared/teams/common/channel-hooks.tsx | 3 +- shared/teams/common/selection-popup.tsx | 3 +- shared/teams/emojis/add-alias.tsx | 44 +- shared/teams/routes.tsx | 6 +- shared/teams/team/member/add-to-channels.tsx | 7 +- shared/teams/team/member/index.new.tsx | 3 +- shared/teams/team/rows/empty-row.tsx | 3 +- shared/teams/team/rows/index.tsx | 9 +- shared/teams/team/rows/invite-row/request.tsx | 3 +- shared/teams/team/settings-tab/index.tsx | 3 +- .../team/settings-tab/retention/index.tsx | 5 +- shared/teams/team/tabs.tsx | 3 +- 148 files changed, 2162 insertions(+), 1871 deletions(-) create mode 100644 shared/chat/make-chat-screen.tsx create mode 100644 shared/constants/chat/helpers.tsx delete mode 100644 shared/constants/chat/index.tsx create mode 100644 shared/stores/chat-shared.tsx create mode 100644 shared/stores/convo-registry.tsx diff --git a/shared/chat/audio/audio-recorder.native.tsx b/shared/chat/audio/audio-recorder.native.tsx index 726ee55ccec9..7bb526791da9 100644 --- a/shared/chat/audio/audio-recorder.native.tsx +++ b/shared/chat/audio/audio-recorder.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {Portal} from '@/common-adapters/portal.native' @@ -364,7 +364,7 @@ const useRecorder = (p: {ampSV: SVN; setShowAudioSend: (s: boolean) => void; sho setStaged(false) setShowAudioSend(false) } - const setCommandStatusInfo = Chat.useChatUIContext(s => s.dispatch.setCommandStatusInfo) + const setCommandStatusInfo = ConvoState.useChatUIContext(s => s.dispatch.setCommandStatusInfo) const startRecording = () => { const checkPerms = async () => { @@ -420,7 +420,7 @@ const useRecorder = (p: {ampSV: SVN; setShowAudioSend: (s: boolean) => void; sho return } - const sendAudioRecording = Chat.useChatContext(s => s.dispatch.sendAudioRecording) + const sendAudioRecording = ConvoState.useChatContext(s => s.dispatch.sendAudioRecording) const sendRecording = () => { const impl = async () => { @@ -702,10 +702,16 @@ const CancelHint = (props: {fadeSV: SVN; dragXSV: SVN; lockedSV: SVN; onCancel: return ( <> - + - + {} ) } - const setConversationStatus = Chat.useChatContext(s => s.dispatch.blockConversation) + const setConversationStatus = ConvoState.useChatContext(s => s.dispatch.blockConversation) const setUserBlocks = (newBlocks: NewBlocksMap) => { // Convert our state block array to action payload. const blocks = [...newBlocks.entries()] diff --git a/shared/chat/blocking/invitation-to-block.tsx b/shared/chat/blocking/invitation-to-block.tsx index e7cf30ec7f34..5d2dacaac26b 100644 --- a/shared/chat/blocking/invitation-to-block.tsx +++ b/shared/chat/blocking/invitation-to-block.tsx @@ -1,28 +1,30 @@ import * as C from '@/constants' +import {isAssertion} from '@/constants/chat/helpers' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import {useCurrentUserState} from '@/stores/current-user' import {navToProfile} from '@/constants/router' const BlockButtons = () => { const navigateAppend = C.Router2.navigateAppend - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) - const team = Chat.useChatContext(s => s.meta.teamname) - const teamID = Chat.useChatContext(s => s.meta.teamID) + const team = ConvoState.useChatContext(s => s.meta.teamname) + const teamID = ConvoState.useChatContext(s => s.meta.teamID) const blockButtonInfo = Chat.useChatState(s => { const blockButtonsMap = s.blockButtonsMap return teamID ? blockButtonsMap.get(teamID) : undefined }) - const participantInfo = Chat.useChatContext(s => s.participants) + const participantInfo = ConvoState.useChatContext(s => s.participants) const currentUser = useCurrentUserState(s => s.username) - const dismissBlockButtons = Chat.useChatContext(s => s.dispatch.dismissBlockButtons) + const dismissBlockButtons = Chat.useChatState(s => s.dispatch.dismissBlockButtons) if (!blockButtonInfo) { return null } const adder = blockButtonInfo.adder const others = (team ? participantInfo.all : participantInfo.name).filter( - person => person !== currentUser && person !== adder && !Chat.isAssertion(person) + person => person !== currentUser && person !== adder && !isAssertion(person) ) const onViewProfile = () => navToProfile(adder) diff --git a/shared/chat/conversation/attachment-fullscreen/hooks.tsx b/shared/chat/conversation/attachment-fullscreen/hooks.tsx index 1ef9c6f81e42..d7790dd38f90 100644 --- a/shared/chat/conversation/attachment-fullscreen/hooks.tsx +++ b/shared/chat/conversation/attachment-fullscreen/hooks.tsx @@ -1,21 +1,23 @@ import * as React from 'react' import * as C from '@/constants' +import {clampImageSize} from '@/constants/chat/helpers' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import {maxWidth, maxHeight} from '../messages/attachment/shared' import {useFSState} from '@/stores/fs' const blankMessage = Chat.makeMessageAttachment({}) export const useData = (initialOrdinal: T.Chat.Ordinal) => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const [ordinal, setOrdinal] = React.useState(initialOrdinal) - const message: T.Chat.MessageAttachment = Chat.useChatContext(s => { + const message: T.Chat.MessageAttachment = ConvoState.useChatContext(s => { const m = s.messageMap.get(ordinal) return m?.type === 'attachment' ? m : blankMessage }) - const loadNextAttachment = Chat.useChatContext(s => s.dispatch.loadNextAttachment) + const loadNextAttachment = ConvoState.useChatContext(s => s.dispatch.loadNextAttachment) const onSwitchAttachment = (backInTime: boolean) => { const f = async () => { if (conversationIDKey !== blankMessage.conversationIDKey) { @@ -37,11 +39,11 @@ export const useData = (initialOrdinal: T.Chat.Ordinal) => { s => s.dispatch.defer.openLocalPathInSystemFileManagerDesktop ) const navigateUp = C.Router2.navigateUp - const showInfoPanel = Chat.useChatContext(s => s.dispatch.showInfoPanel) - const attachmentDownload = Chat.useChatContext(s => s.dispatch.attachmentDownload) + const showInfoPanel = ConvoState.useChatContext(s => s.dispatch.showInfoPanel) + const attachmentDownload = ConvoState.useChatContext(s => s.dispatch.attachmentDownload) const {downloadPath, fileURL: path, fullHeight, fullWidth, fileType} = message const {previewHeight, previewURL: previewPath, previewWidth, title, transferProgress} = message - const {height: clampedHeight, width: clampedWidth} = Chat.clampImageSize( + const {height: clampedHeight, width: clampedWidth} = clampImageSize( previewWidth, previewHeight, maxWidth, diff --git a/shared/chat/conversation/attachment-get-titles.tsx b/shared/chat/conversation/attachment-get-titles.tsx index ea3cf94dee47..a617e0828db0 100644 --- a/shared/chat/conversation/attachment-get-titles.tsx +++ b/shared/chat/conversation/attachment-get-titles.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as React from 'react' import * as Kb from '@/common-adapters' @@ -40,8 +41,8 @@ const Container = (ownProps: OwnProps) => { const noDragDrop = ownProps.noDragDrop ?? false const selectConversationWithReason = ownProps.selectConversationWithReason const navigateUp = C.Router2.navigateUp - const navigateToThread = Chat.useChatContext(s => s.dispatch.navigateToThread) - const attachmentUploadCanceled = Chat.useChatContext(s => s.dispatch.attachmentUploadCanceled) + const navigateToThread = ConvoState.useChatContext(s => s.dispatch.navigateToThread) + const attachmentUploadCanceled = ConvoState.useChatContext(s => s.dispatch.attachmentUploadCanceled) const onCancel = () => { attachmentUploadCanceled( pathAndOutboxIDs.reduce((l: Array, {outboxID}) => { @@ -54,8 +55,8 @@ const Container = (ownProps: OwnProps) => { navigateUp() } const clearModals = C.Router2.clearModals - const attachmentsUpload = Chat.useChatContext(s => s.dispatch.attachmentsUpload) - const attachFromDragAndDrop = Chat.useChatContext(s => s.dispatch.attachFromDragAndDrop) + const attachmentsUpload = ConvoState.useChatContext(s => s.dispatch.attachmentsUpload) + const attachFromDragAndDrop = ConvoState.useChatContext(s => s.dispatch.attachFromDragAndDrop) const _onSubmit = (titles: Array, spoiler: boolean) => { tlfName || noDragDrop diff --git a/shared/chat/conversation/bot/confirm.tsx b/shared/chat/conversation/bot/confirm.tsx index 85ecb3d7a225..35738967d1c7 100644 --- a/shared/chat/conversation/bot/confirm.tsx +++ b/shared/chat/conversation/bot/confirm.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import {useBotConversationIDKey, useRefreshBotMembershipOnSuccess} from './install' @@ -14,8 +14,8 @@ const ConfirmBotRemoveImpl = (props: {botUsername: string; teamID?: T.Teams.Team const {botUsername, teamID} = props const clearModals = C.Router2.clearModals const error = C.Waiting.useAnyErrors(C.waitingKeyChatBotRemove) - const removeBotMember = Chat.useChatContext(s => s.dispatch.removeBotMember) - const conversationIDKey = Chat.useChatContext(s => s.id) + const removeBotMember = ConvoState.useChatContext(s => s.dispatch.removeBotMember) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const onClose = () => { clearModals() } @@ -47,9 +47,9 @@ const ConfirmBotRemove = (props: Props) => { const {teamID, botUsername} = props const conversationIDKey = useBotConversationIDKey(props.conversationIDKey, teamID) return conversationIDKey ? ( - + - + ) : null } diff --git a/shared/chat/conversation/bot/install.tsx b/shared/chat/conversation/bot/install.tsx index 01fb8dec5d2f..cc89c5d4cc80 100644 --- a/shared/chat/conversation/bot/install.tsx +++ b/shared/chat/conversation/bot/install.tsx @@ -1,7 +1,7 @@ import * as C from '@/constants' import * as ChatCommon from '@/constants/chat/common' import * as Meta from '@/constants/chat/meta' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as Teams from '@/stores/teams' import * as React from 'react' @@ -28,7 +28,7 @@ export const useRefreshBotMembershipOnSuccess = ( const wasWaitingRef = React.useRef(waiting) const updateCachedBotMember = Teams.useTeamsState(s => s.dispatch.updateCachedBotMember) const previewConversationByID = C.useRPC(T.RPCChat.localPreviewConversationByIDLocalRpcPromise) - const setParticipants = Chat.useChatContext(s => s.dispatch.setParticipants) + const setParticipants = ConvoState.useChatContext(s => s.dispatch.setParticipants) const teamIDToRefresh = teamID && teamID !== T.Teams.noTeamID ? teamID : undefined React.useEffect(() => { @@ -75,7 +75,6 @@ export const useBotConversationIDKey = (inConvIDKey?: T.Chat.ConversationIDKey, const cleanInConvIDKey = T.Chat.isValidConversationIDKey(inConvIDKey ?? '') ? inConvIDKey : undefined const [conversationIDKey, setConversationIDKey] = React.useState(cleanInConvIDKey) const findGeneralConvIDFromTeamID = C.useRPC(T.RPCChat.localFindGeneralConvFromTeamIDRpcPromise) - const metasReceived = Chat.useChatState(s => s.dispatch.metasReceived) const requestIDRef = React.useRef(0) React.useEffect(() => { @@ -98,7 +97,7 @@ export const useBotConversationIDKey = (inConvIDKey?: T.Chat.ConversationIDKey, if (!meta) { return } - metasReceived([meta]) + ConvoState.metasReceived([meta]) setConversationIDKey(meta.conversationIDKey) }, () => {} @@ -108,7 +107,7 @@ export const useBotConversationIDKey = (inConvIDKey?: T.Chat.ConversationIDKey, requestIDRef.current += 1 } } - }, [cleanInConvIDKey, findGeneralConvIDFromTeamID, metasReceived, teamID]) + }, [cleanInConvIDKey, findGeneralConvIDFromTeamID, teamID]) return conversationIDKey } @@ -125,9 +124,9 @@ const InstallBotPopupLoader = (props: LoaderProps) => { const conversationIDKey = useBotConversationIDKey(inConvIDKey, teamID) if (!conversationIDKey) return null return ( - + - + ) } @@ -151,7 +150,7 @@ const InstallBotPopup = (props: Props) => { const [disableDone, setDisableDone] = React.useState(false) const [botPublicCommands, setBotPublicCommands] = React.useState() - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const commandsFromMeta = ( meta.botCommands.typ === T.RPCChat.ConversationCommandGroupsTyp.custom ? meta.botCommands.custom.commands || blankCommands @@ -165,7 +164,7 @@ const InstallBotPopup = (props: Props) => { : botPublicCommands const featured = useFeaturedBot(botUsername) - const teamRole = Chat.useChatContext(s => s.botTeamRoleMap.get(botUsername)) + const teamRole = ConvoState.useChatContext(s => s.botTeamRoleMap.get(botUsername)) const inTeam = teamRole !== undefined ? !!teamRole : undefined const inTeamUnrestricted = inTeam && teamRole === 'bot' const isBot = teamRole === 'bot' || teamRole === 'restrictedbot' ? true : undefined @@ -173,7 +172,7 @@ const InstallBotPopup = (props: Props) => { const readOnly = Teams.useTeamsState(s => meta.teamname ? !Teams.getCanPerformByID(s, meta.teamID).manageBots : false ) - const settings = Chat.useChatContext(s => s.botSettings.get(botUsername) ?? undefined) + const settings = ConvoState.useChatContext(s => s.botSettings.get(botUsername) ?? undefined) let teamname: string | undefined let teamID: T.Teams.TeamID = T.Teams.noTeamID let refreshTeamID: T.Teams.TeamID | undefined @@ -190,7 +189,7 @@ const InstallBotPopup = (props: Props) => { // dispatch const clearModals = C.Router2.clearModals const navigateUp = C.Router2.navigateUp - const addBotMember = Chat.useChatContext(s => s.dispatch.addBotMember) + const addBotMember = ConvoState.useChatContext(s => s.dispatch.addBotMember) const [pendingMutation, setPendingMutation] = React.useState<'add' | 'edit' | undefined>() const onLearn = () => { openURL('https://book.keybase.io/docs/chat/restricted-bots') @@ -203,7 +202,7 @@ const InstallBotPopup = (props: Props) => { setPendingMutation('add') addBotMember(botUsername, installWithCommands, installWithMentions, installWithRestrict, installInConvs) } - const editBotSettings = Chat.useChatContext(s => s.dispatch.editBotSettings) + const editBotSettings = ConvoState.useChatContext(s => s.dispatch.editBotSettings) const onEdit = () => { if (!conversationIDKey) { return @@ -226,8 +225,8 @@ const InstallBotPopup = (props: Props) => { navigateAppend({name: 'feedback', params: {}}) } - const refreshBotSettings = Chat.useChatContext(s => s.dispatch.refreshBotSettings) - const refreshBotRoleInConv = Chat.useChatContext(s => s.dispatch.refreshBotRoleInConv) + const refreshBotSettings = ConvoState.useChatContext(s => s.dispatch.refreshBotSettings) + const refreshBotRoleInConv = ConvoState.useChatContext(s => s.dispatch.refreshBotRoleInConv) // lifecycle React.useEffect(() => { diff --git a/shared/chat/conversation/bot/search.tsx b/shared/chat/conversation/bot/search.tsx index d00862fbab24..bb0db6ff60c1 100644 --- a/shared/chat/conversation/bot/search.tsx +++ b/shared/chat/conversation/bot/search.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as React from 'react' import debounce from 'lodash/debounce' @@ -29,7 +29,7 @@ type Item = type Section = Omit, 'title'> & {title: string} const SearchBotPopup = (props: Props) => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const teamID = props.teamID const [lastQuery, setLastQuery] = React.useState('') const [botSearchResults, setBotSearchResults] = React.useState( diff --git a/shared/chat/conversation/bottom-banner.tsx b/shared/chat/conversation/bottom-banner.tsx index c98694933a77..3227f5b0a68a 100644 --- a/shared/chat/conversation/bottom-banner.tsx +++ b/shared/chat/conversation/bottom-banner.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import type * as React from 'react' import {openSMS as _openSMS} from '@/util/misc' @@ -12,7 +12,7 @@ const installMessage = `I sent you encrypted messages on Keybase. You can instal const Invite = () => { const linkUrlProps = Kb.useClickURL('https://keybase.io/app') - const participantInfo = Chat.useChatContext(s => s.participants) + const participantInfo = ConvoState.useChatContext(s => s.participants) const participantInfoAll = participantInfo.all const users = participantInfoAll.filter(p => p.includes('@')) @@ -33,7 +33,7 @@ const Invite = () => { const usernameToContactName = participantInfo.contactName - const onDismiss = Chat.useChatContext(s => s.dispatch.dismissBottomBanner) + const onDismiss = ConvoState.useChatContext(s => s.dispatch.dismissBottomBanner) const theirName = users.length === 1 @@ -93,7 +93,7 @@ const Invite = () => { const Broken = () => { const following = useFollowerState(s => s.following) const infoMap = useUsersState(s => s.infoMap) - const participantInfo = Chat.useChatContext(s => s.participants) + const participantInfo = ConvoState.useChatContext(s => s.participants) const users = participantInfo.all.filter(p => following.has(p) && infoMap.get(p)?.broken) return } @@ -101,9 +101,9 @@ const Broken = () => { const BannerContainer = function BannerContainer() { const following = useFollowerState(s => s.following) const infoMap = useUsersState(s => s.infoMap) - const dismissed = Chat.useChatContext(s => s.dismissedInviteBanners) - const participantInfo = Chat.useChatContext(s => s.participants) - const type = Chat.useChatContext(s => { + const dismissed = ConvoState.useChatContext(s => s.dismissedInviteBanners) + const participantInfo = ConvoState.useChatContext(s => s.participants) + const type = ConvoState.useChatContext(s => { const teamType = s.meta.teamType if (teamType !== 'adhoc') { return 'none' diff --git a/shared/chat/conversation/command-markdown.tsx b/shared/chat/conversation/command-markdown.tsx index 751f669af955..43f4a2257bc2 100644 --- a/shared/chat/conversation/command-markdown.tsx +++ b/shared/chat/conversation/command-markdown.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' const CommandMarkdown = () => { - const md = Chat.useChatContext(s => s.commandMarkdown) + const md = ConvoState.useChatContext(s => s.commandMarkdown) const body = md?.body ?? '' const title = md?.title ?? undefined return ( diff --git a/shared/chat/conversation/command-status.tsx b/shared/chat/conversation/command-status.tsx index 83f56446412d..faa5bd2e2697 100644 --- a/shared/chat/conversation/command-status.tsx +++ b/shared/chat/conversation/command-status.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import {useConfigState} from '@/stores/config' @@ -10,11 +10,11 @@ const empty = { } const Container = () => { - const info = Chat.useChatUIContext(s => s.commandStatus) + const info = ConvoState.useChatUIContext(s => s.commandStatus) const _info = info || empty const onOpenAppSettings = useConfigState(s => s.dispatch.defer.openAppSettings) - const setCommandStatusInfo = Chat.useChatUIContext(s => s.dispatch.setCommandStatusInfo) + const setCommandStatusInfo = ConvoState.useChatUIContext(s => s.dispatch.setCommandStatusInfo) const onCancel = () => { setCommandStatusInfo() } diff --git a/shared/chat/conversation/container.tsx b/shared/chat/conversation/container.tsx index f27584c978ed..8406c0a05c8d 100644 --- a/shared/chat/conversation/container.tsx +++ b/shared/chat/conversation/container.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import Normal from './normal/container' import NoConversation from './no-conversation' import Error from './error' @@ -8,7 +9,7 @@ import Rekey from './rekey/container' import type {ThreadSearchRouteProps} from './thread-search-route' const Conversation = function Conversation(_: ThreadSearchRouteProps) { - const type = Chat.useChatContext(s => { + const type = ConvoState.useChatContext(s => { const meta = s.meta switch (s.id) { case Chat.noConversationIDKey: diff --git a/shared/chat/conversation/error.tsx b/shared/chat/conversation/error.tsx index 80862bb4bb99..825d6cb1bd86 100644 --- a/shared/chat/conversation/error.tsx +++ b/shared/chat/conversation/error.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' const ConversationError = () => { - const text = Chat.useChatContext(s => s.meta.snippet ?? '') + const text = ConvoState.useChatContext(s => s.meta.snippet ?? '') return ( There was an error loading this conversation. diff --git a/shared/chat/conversation/fwd-msg.tsx b/shared/chat/conversation/fwd-msg.tsx index 04bb51e51c34..0896b04e2dc9 100644 --- a/shared/chat/conversation/fwd-msg.tsx +++ b/shared/chat/conversation/fwd-msg.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' @@ -13,9 +14,9 @@ type Props = {ordinal: T.Chat.Ordinal} type PickerState = 'picker' | 'title' const TeamPicker = (props: Props) => { - const srcConvID = Chat.useChatContext(s => s.id) + const srcConvID = ConvoState.useChatContext(s => s.id) const ordinal = props.ordinal - const message = Chat.useChatContext(s => s.messageMap.get(ordinal)) + const message = ConvoState.useChatContext(s => s.messageMap.get(ordinal)) const [pickerState, setPickerState] = React.useState('picker') const [term, setTerm] = React.useState('') const dstConvIDRef = React.useRef(undefined) diff --git a/shared/chat/conversation/giphy/hooks.tsx b/shared/chat/conversation/giphy/hooks.tsx index 128cab51bb5e..232dd3013d2b 100644 --- a/shared/chat/conversation/giphy/hooks.tsx +++ b/shared/chat/conversation/giphy/hooks.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' export const useHooks = () => { - const giphy = Chat.useChatUIContext(s => s.giphyResult) - const onClick = Chat.useChatContext(s => s.dispatch.giphySend) + const giphy = ConvoState.useChatUIContext(s => s.giphyResult) + const onClick = ConvoState.useChatContext(s => s.dispatch.giphySend) return { galleryURL: giphy?.galleryUrl ?? '', onClick, diff --git a/shared/chat/conversation/header-area/index.native.tsx b/shared/chat/conversation/header-area/index.native.tsx index d2fac88cf09a..447322afcc59 100644 --- a/shared/chat/conversation/header-area/index.native.tsx +++ b/shared/chat/conversation/header-area/index.native.tsx @@ -1,5 +1,7 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import {chatStores} from '@/stores/convo-registry' +import * as ConvoState from '@/stores/convostate' import {getConvoState} from '@/stores/convostate' import * as Kb from '@/common-adapters' import type {HeaderBackButtonProps} from '@react-navigation/elements' @@ -12,9 +14,10 @@ import {useSafeAreaFrame} from 'react-native-safe-area-context' import {useUsersState} from '@/stores/users' import {useCurrentUserState} from '@/stores/current-user' import {navToProfile} from '@/constants/router' +import * as React from 'react' export const HeaderAreaRight = () => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const pendingWaiting = conversationIDKey === Chat.pendingWaitingConversationIDKey || conversationIDKey === Chat.pendingErrorConversationIDKey @@ -38,9 +41,9 @@ export const HeaderAreaRight = () => { // // ) : null - const showInfoPanel = Chat.useChatContext(s => s.dispatch.showInfoPanel) + const showInfoPanel = ConvoState.useChatContext(s => s.dispatch.showInfoPanel) const onShowInfoPanel = () => showInfoPanel(true, undefined) - const toggleThreadSearch = Chat.useChatContext(s => s.dispatch.toggleThreadSearch) + const toggleThreadSearch = ConvoState.useChatContext(s => s.dispatch.toggleThreadSearch) const onToggleThreadSearch = () => { // fix a race with the keyboard going away and coming back quickly Keyboard.dismiss() @@ -68,8 +71,8 @@ enum HeaderType { } const HeaderBranchContainer = function HeaderBranchContainer() { - const participantInfo = Chat.useChatContext(s => s.participants) - const type = Chat.useChatContext(s => { + const participantInfo = ConvoState.useChatContext(s => s.participants) + const type = ConvoState.useChatContext(s => { const meta = s.meta const teamName = meta.teamname if (teamName) { @@ -110,9 +113,9 @@ export const headerNavigationOptions = (route: {params?: {conversationIDKey?: st headerLeft: (props: HeaderBackButtonProps) => { const {labelStyle, ...rest} = props return ( - + - + ) }, } @@ -141,15 +144,15 @@ export const headerNavigationOptions = (route: {params?: {conversationIDKey?: st } : { headerRight: () => ( - + - + ), }), headerTitle: () => ( - + - + ), } } @@ -157,8 +160,19 @@ export const headerNavigationOptions = (route: {params?: {conversationIDKey?: st export const useBackBadge = () => { const visiblePath = C.Router2.getVisiblePath() const onTopOfInbox = visiblePath[visiblePath.length - 2]?.name === 'chatRoot' - const conversationIDKey = Chat.useChatContext(s => s.id) - const badgeNumber = Chat.useChatState(s => s.getBackCount(conversationIDKey)) + const conversationIDKey = ConvoState.useChatContext(s => s.id) + const badgeStateVersion = Chat.useChatState(s => s.badgeStateVersion) + const badgeNumber = React.useMemo(() => { + void badgeStateVersion + let count = 0 + for (const store of chatStores.values()) { + const {badge, id} = store.getState() + if (id !== conversationIDKey) { + count += badge + } + } + return count + }, [badgeStateVersion, conversationIDKey]) if (!onTopOfInbox) return 0 return badgeNumber } @@ -167,8 +181,8 @@ const shhIconColor = Kb.Styles.globalColors.black_20 const shhIconFontSize = 24 const ShhIcon = function ShhIcon() { - const isMuted = Chat.useChatContext(s => s.meta.isMuted) - const mute = Chat.useChatContext(s => s.dispatch.mute) + const isMuted = ConvoState.useChatContext(s => s.meta.isMuted) + const mute = ConvoState.useChatContext(s => s.dispatch.mute) const unMuteConversation = () => { mute(false) } @@ -191,7 +205,7 @@ const useMaxWidthStyle = () => { } const ChannelHeader = () => { - const {channelname, smallTeam, teamname, teamID} = Chat.useChatContext( + const {channelname, smallTeam, teamname, teamID} = ConvoState.useChatContext( C.useShallow(s => { const meta = s.meta const {channelname, teamname, teamType, teamID} = meta @@ -241,8 +255,8 @@ const emptyArray = new Array() const UsernameHeader = () => { const you = useCurrentUserState(s => s.username) const infoMap = useUsersState(s => s.infoMap) - const participantInfo = Chat.useChatContext(s => s.participants) - const {participants, theirFullname} = Chat.useChatContext( + const participantInfo = ConvoState.useChatContext(s => s.participants) + const {participants, theirFullname} = ConvoState.useChatContext( C.useShallow(s => { const meta = s.meta const participants = meta.teamname ? emptyArray : participantInfo.name @@ -290,8 +304,8 @@ const UsernameHeader = () => { } const PhoneOrEmailHeader = () => { - const participantInfo = Chat.useChatContext(s => s.participants) - const meta = Chat.useChatContext(s => s.meta) + const participantInfo = ConvoState.useChatContext(s => s.participants) + const meta = ConvoState.useChatContext(s => s.meta) const participants = (meta.teamname ? null : participantInfo.name) || emptyArray const phoneOrEmail = participants.find(s => s.endsWith('@phone') || s.endsWith('@email')) || '' const formattedPhoneOrEmail = assertionToDisplay(phoneOrEmail) diff --git a/shared/chat/conversation/info-panel/add-people.tsx b/shared/chat/conversation/info-panel/add-people.tsx index a24c97fbf8eb..57b274394c90 100644 --- a/shared/chat/conversation/info-panel/add-people.tsx +++ b/shared/chat/conversation/info-panel/add-people.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import {useTeamsState} from '@/stores/teams' import * as Kb from '@/common-adapters' @@ -9,9 +9,9 @@ type Props = { const AddPeople = (p: Props) => { const {isGeneralChannel, isAdmin} = p - const teamID = Chat.useChatContext(s => s.meta.teamID) + const teamID = ConvoState.useChatContext(s => s.meta.teamID) const startAddMembersWizard = useTeamsState(s => s.dispatch.startAddMembersWizard) - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onAddPeople = () => { startAddMembersWizard(teamID) } diff --git a/shared/chat/conversation/info-panel/add-to-channel.tsx b/shared/chat/conversation/info-panel/add-to-channel.tsx index 9993717cee83..535f988be09b 100644 --- a/shared/chat/conversation/info-panel/add-to-channel.tsx +++ b/shared/chat/conversation/info-panel/add-to-channel.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as React from 'react' import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' @@ -15,7 +15,7 @@ type Props = {teamID: T.Teams.TeamID} const AddToChannel = (props: Props) => { const {teamID} = props const nav = useSafeNavigation() - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const [toAdd, setToAdd] = React.useState(new Set()) const [filter, setFilter] = React.useState('') @@ -80,9 +80,24 @@ const AddToChannel = (props: Props) => { title: `Add to #${channelname}`, }) return () => { - useModalHeaderState.setState({actionEnabled: false, actionWaiting: false, onAction: undefined, title: ''}) + useModalHeaderState.setState({ + actionEnabled: false, + actionWaiting: false, + onAction: undefined, + title: '', + }) } - }, [channelname, teamID, toAdd, toAdd.size, waiting, addToChannel, conversationIDKey, loadTeamChannelList, nav]) + }, [ + channelname, + teamID, + toAdd, + toAdd.size, + waiting, + addToChannel, + conversationIDKey, + loadTeamChannelList, + nav, + ]) return ( <> @@ -154,21 +169,16 @@ const AddToChannel = (props: Props) => { {Kb.Styles.isMobile ? null : ( - - - - + + + + )} diff --git a/shared/chat/conversation/info-panel/attachments.tsx b/shared/chat/conversation/info-panel/attachments.tsx index c05067a8d0e7..3042e9f05a6a 100644 --- a/shared/chat/conversation/info-panel/attachments.tsx +++ b/shared/chat/conversation/info-panel/attachments.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import {zoomImage} from '@/constants/chat/helpers' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import type {StylesTextCrossPlatform} from '@/common-adapters/text.shared' import * as T from '@/constants/types' @@ -435,8 +436,8 @@ export const useAttachmentSections = ( T.RPCChat.GalleryItemTyp.media ) const [lastSAV, setLastSAV] = React.useState(selectedAttachmentView) - const loadAttachmentView = Chat.useChatContext(s => s.dispatch.loadAttachmentView) - const loadMessagesCentered = Chat.useChatContext(s => s.dispatch.loadMessagesCentered) + const loadAttachmentView = ConvoState.useChatContext(s => s.dispatch.loadAttachmentView) + const loadMessagesCentered = ConvoState.useChatContext(s => s.dispatch.loadMessagesCentered) const clearModals = C.Router2.clearModals const jumpToAttachment = (messageID: T.Chat.MessageID) => { @@ -463,7 +464,7 @@ export const useAttachmentSections = ( } }, [lastSAV, loadAttachmentView, loadImmediately, selectedAttachmentView]) - const attachmentView = Chat.useChatContext(s => s.attachmentViewMap) + const attachmentView = ConvoState.useChatContext(s => s.attachmentViewMap) const attachmentInfo = attachmentView.get(selectedAttachmentView) const fromMsgID = attachmentInfo ? getFromMsgID(attachmentInfo) : undefined @@ -477,11 +478,11 @@ export const useAttachmentSections = ( loadAttachmentView(selectedAttachmentView) } - const attachmentPreviewSelect = Chat.useChatContext(s => s.dispatch.attachmentPreviewSelect) + const attachmentPreviewSelect = ConvoState.useChatContext(s => s.dispatch.attachmentPreviewSelect) const onMediaClick = (message: T.Chat.MessageAttachment) => attachmentPreviewSelect(message.ordinal) - const attachmentDownload = Chat.useChatContext(s => s.dispatch.attachmentDownload) - const messageAttachmentNativeShare = Chat.useChatContext(s => s.dispatch.messageAttachmentNativeShare) + const attachmentDownload = ConvoState.useChatContext(s => s.dispatch.attachmentDownload) + const messageAttachmentNativeShare = ConvoState.useChatContext(s => s.dispatch.messageAttachmentNativeShare) const onDocDownload = (message: T.Chat.MessageAttachment) => { if (Kb.Styles.isMobile) { @@ -581,7 +582,7 @@ export const useAttachmentSections = ( maxMediaThumbSize, width: thumb.width, }, - sizing: Chat.zoomImage(thumb.width, thumb.height, maxMediaThumbSize), + sizing: zoomImage(thumb.width, thumb.height, maxMediaThumbSize), thumb, })) const dataChunked = useFlexWrap ? [dataUnchunked] : chunk(dataUnchunked, rowSize) diff --git a/shared/chat/conversation/info-panel/bot.tsx b/shared/chat/conversation/info-panel/bot.tsx index 6bec1c7efa10..02d2d2eaa9b9 100644 --- a/shared/chat/conversation/info-panel/bot.tsx +++ b/shared/chat/conversation/info-panel/bot.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import * as React from 'react' @@ -35,8 +35,8 @@ type Section = Kb.SectionType const AddToChannel = (props: AddToChannelProps) => { const {conversationIDKey, username} = props - const settings = Chat.useChatContext(s => s.botSettings.get(username)) - const editBotSettings = Chat.useChatContext(s => s.dispatch.editBotSettings) + const settings = ConvoState.useChatContext(s => s.botSettings.get(username)) + const editBotSettings = ConvoState.useChatContext(s => s.dispatch.editBotSettings) return ( { const {ownerTeam, ownerUser} = props const {onClick, firstItem, isSelected} = props const {conversationIDKey, showChannelAdd, showTeamAdd} = props - const refreshBotSettings = Chat.useChatContext(s => s.dispatch.refreshBotSettings) + const refreshBotSettings = ConvoState.useChatContext(s => s.dispatch.refreshBotSettings) const primaryColor = isSelected ? Kb.Styles.globalColors.white : Kb.Styles.globalColors.black const secondaryColor = isSelected ? Kb.Styles.globalColors.white : undefined React.useEffect(() => { @@ -90,7 +90,12 @@ export const Bot = (props: BotProps) => { const lower = ( {description !== '' && ( - onClick(botUsername)}> + onClick(botUsername)} + > {description} )} @@ -181,9 +186,9 @@ type Props = { } const BotTab = (props: Props) => { - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const {teamID, teamname, teamType, botAliases} = meta - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const yourOperations = Teams.useTeamsState(s => (teamname ? Teams.getCanPerformByID(s, teamID) : undefined)) let canManageBots = false if (teamname) { @@ -192,7 +197,7 @@ const BotTab = (props: Props) => { canManageBots = true } const adhocTeam = teamType === 'adhoc' - const participantInfo = Chat.useChatContext(s => s.participants) + const participantInfo = ConvoState.useChatContext(s => s.participants) const teamMembers = Teams.useTeamsState(s => s.teamIDToMembers.get(teamID)) const participantsAll = participantInfo.all @@ -245,7 +250,7 @@ const BotTab = (props: Props) => { const botsInTeam: string[] = botUsernames.filter(b => !botsInConv.includes(b)) - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onBotAdd = () => { navigateAppend(conversationIDKey => ({name: 'chatSearchBots', params: {conversationIDKey}})) } diff --git a/shared/chat/conversation/info-panel/common.tsx b/shared/chat/conversation/info-panel/common.tsx index 5d9352307d37..c76caedace7b 100644 --- a/shared/chat/conversation/info-panel/common.tsx +++ b/shared/chat/conversation/info-panel/common.tsx @@ -1,4 +1,4 @@ -import type * as Chat from '@/stores/chat' +import type {ConvoState} from '@/stores/convostate' import {useTeamsState} from '@/stores/teams' import * as Styles from '@/styles' import type * as T from '@/constants/types' @@ -33,8 +33,8 @@ export const useTeamHumans = (teamID: T.Teams.TeamID) => { } export const useHumans = ( - participantInfo: Chat.ConvoState['participants'], - meta: Chat.ConvoState['meta'] + participantInfo: ConvoState['participants'], + meta: ConvoState['meta'] ) => { const {teamType, teamID} = meta const {bots, teamHumanCount} = useTeamHumans(teamID) diff --git a/shared/chat/conversation/info-panel/header.tsx b/shared/chat/conversation/info-panel/header.tsx index 4854c89f4bf9..20627e7a52f1 100644 --- a/shared/chat/conversation/info-panel/header.tsx +++ b/shared/chat/conversation/info-panel/header.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import InfoPanelMenu from './menu' @@ -8,11 +8,11 @@ import AddPeople from './add-people' const gearIconSize = Kb.Styles.isMobile ? 24 : 16 const TeamHeader = () => { - const conversationIDKey = Chat.useChatContext(s => s.id) - const meta = Chat.useChatContext(s => s.meta) + const conversationIDKey = ConvoState.useChatContext(s => s.id) + const meta = ConvoState.useChatContext(s => s.meta) const {teamname, teamID, channelname, descriptionDecorated: description, membershipType, teamType} = meta - const participants = Chat.useChatContext(s => s.participants) - const onJoinChannel = Chat.useChatContext(s => s.dispatch.joinConversation) + const participants = ConvoState.useChatContext(s => s.participants) + const onJoinChannel = ConvoState.useChatContext(s => s.dispatch.joinConversation) const {channelHumans, teamHumanCount} = InfoPanelCommon.useHumans(participants, meta) const yourOperations = Teams.useTeamsState(s => (teamname ? Teams.getCanPerformByID(s, teamID) : undefined)) @@ -28,7 +28,7 @@ const TeamHeader = () => { const makePopup = (p: Kb.Popup2Parms) => { const {attachTo, hidePopup} = p return ( - + { isSmallTeam={isSmallTeam} visible={true} /> - + ) } const {showPopup, popup, popupAnchor} = Kb.usePopup2(makePopup) @@ -139,7 +139,7 @@ const TeamHeader = () => { } export const AdhocHeader = () => { - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onShowNewTeamDialog = () => { navigateAppend(conversationIDKey => ({ name: 'chatShowNewTeamDialog', diff --git a/shared/chat/conversation/info-panel/index.tsx b/shared/chat/conversation/info-panel/index.tsx index 40c7683ac51d..0615a94923c5 100644 --- a/shared/chat/conversation/info-panel/index.tsx +++ b/shared/chat/conversation/info-panel/index.tsx @@ -1,4 +1,5 @@ import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as Teams from '@/stores/teams' import * as React from 'react' @@ -17,8 +18,8 @@ type Props = { const InfoPanelConnector = (ownProps: Props) => { const initialTab = ownProps.tab - const conversationIDKey = Chat.useChatContext(s => s.id) - const meta = Chat.useConvoState(conversationIDKey, s => s.meta) + const conversationIDKey = ConvoState.useChatContext(s => s.id) + const meta = ConvoState.useConvoState(conversationIDKey, s => s.meta) const shouldNavigateOut = meta.conversationIDKey === Chat.noConversationIDKey const yourRole = Teams.useTeamsState(s => Teams.getRole(s, meta.teamID)) const isPreview = meta.membershipType === 'youArePreviewing' @@ -28,8 +29,8 @@ const InfoPanelConnector = (ownProps: Props) => { const [selectedTab, onSelectTab] = React.useState(initialTab ?? 'members') const [lastSNO, setLastSNO] = React.useState(shouldNavigateOut) - const showInfoPanel = Chat.useChatContext(s => s.dispatch.showInfoPanel) - const clearAttachmentView = Chat.useConvoState(conversationIDKey, s => s.dispatch.clearAttachmentView) + const showInfoPanel = ConvoState.useChatContext(s => s.dispatch.showInfoPanel) + const clearAttachmentView = ConvoState.useConvoState(conversationIDKey, s => s.dispatch.clearAttachmentView) React.useEffect(() => { return () => { // Only call showInfoPanel(false) on mobile where the panel is a separate route. diff --git a/shared/chat/conversation/info-panel/members.tsx b/shared/chat/conversation/info-panel/members.tsx index 8e0014b12ad5..5d24907d0014 100644 --- a/shared/chat/conversation/info-panel/members.tsx +++ b/shared/chat/conversation/info-panel/members.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import {getBotsAndParticipants} from '@/constants/chat/helpers' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import * as React from 'react' import * as Kb from '@/common-adapters' @@ -30,9 +31,9 @@ type Item = type Section = Kb.SectionType const MembersTab = (props: Props) => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const infoMap = useUsersState(s => s.infoMap) - const {channelname, teamID, teamname} = Chat.useChatContext( + const {channelname, teamID, teamname} = ConvoState.useChatContext( C.useShallow(s => { const {meta} = s const {teamID, channelname, teamname} = meta @@ -44,9 +45,9 @@ const MembersTab = (props: Props) => { const isGeneral = channelname === 'general' const showAuditingBanner = isGeneral && !teamMembers const refreshParticipants = C.useRPC(T.RPCChat.localRefreshParticipantsRpcPromise) - const participantInfo = Chat.useChatContext(s => s.participants) - const participants = Chat.useChatContext( - C.useShallow(s => Chat.getBotsAndParticipants(s.meta, s.participants, teamMembers).participants) + const participantInfo = ConvoState.useChatContext(s => s.participants) + const participants = ConvoState.useChatContext( + C.useShallow(s => getBotsAndParticipants(s.meta, s.participants, teamMembers).participants) ) const [lastTeamName, setLastTeamName] = React.useState('') React.useEffect(() => { diff --git a/shared/chat/conversation/info-panel/menu.tsx b/shared/chat/conversation/info-panel/menu.tsx index b6af1a99e4e7..b43dfaffeb54 100644 --- a/shared/chat/conversation/info-panel/menu.tsx +++ b/shared/chat/conversation/info-panel/menu.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as Teams from '@/stores/teams' import type * as React from 'react' @@ -29,8 +30,8 @@ const useData = (p: {isSmallTeam: boolean; pteamID: string | undefined}) => { const {isSmallTeam, pteamID} = p const username = useCurrentUserState(s => s.username) const infoMap = useUsersState(s => s.infoMap) - const participantInfo = Chat.useChatContext(s => s.participants) - const meta = Chat.useChatContext(s => s.meta) + const participantInfo = ConvoState.useChatContext(s => s.participants) + const meta = ConvoState.useChatContext(s => s.meta) const teamMeta = Teams.useTeamsState(s => (pteamID ? Teams.getTeamMeta(s, pteamID) : undefined)) const manageChannelsTitle = isSmallTeam ? 'Create channels...' : 'Browse all channels' const manageChannelsSubtitle = isSmallTeam ? 'Turns this into a big team' : '' @@ -102,7 +103,7 @@ const InfoPanelMenuConnector = function InfoPanelMenuConnector(p: OwnProps) { const onAddPeople = () => { teamID && startAddMembersWizard(teamID) } - const chatNavigateAppend = Chat.useChatNavigateAppend() + const chatNavigateAppend = ConvoState.useChatNavigateAppend() const routerNavigateAppend = C.Router2.navigateAppend const onBlockConv = () => { chatNavigateAppend(conversationIDKey => ({ @@ -116,20 +117,20 @@ const InfoPanelMenuConnector = function InfoPanelMenuConnector(p: OwnProps) { })) } - const onJoinChannel = Chat.useChatContext(s => s.dispatch.joinConversation) - const onLeaveChannel = Chat.useChatContext(s => s.dispatch.leaveConversation) + const onJoinChannel = ConvoState.useChatContext(s => s.dispatch.joinConversation) + const onLeaveChannel = ConvoState.useChatContext(s => s.dispatch.leaveConversation) const onLeaveTeam = () => teamID && chatNavigateAppend(() => ({name: 'teamReallyLeaveTeam', params: {teamID}})) const onManageChannels = () => { manageChatChannels(teamID) addTeamWithChosenChannels(teamID) } const clearModals = C.Router2.clearModals - const markTeamAsRead = Chat.useChatContext(s => s.dispatch.markTeamAsRead) + const markTeamAsRead = ConvoState.useChatContext(s => s.dispatch.markTeamAsRead) const onMarkAsRead = () => { clearModals() markTeamAsRead(teamID) } - const setMarkAsUnread = Chat.useChatContext(s => s.dispatch.setMarkAsUnread) + const setMarkAsUnread = ConvoState.useChatContext(s => s.dispatch.setMarkAsUnread) const onMarkAsUnread = () => { clearModals() setMarkAsUnread() @@ -138,11 +139,11 @@ const InfoPanelMenuConnector = function InfoPanelMenuConnector(p: OwnProps) { clearModals() chatNavigateAppend(() => ({name: 'team', params: {teamID}})) } - const hideConversation = Chat.useChatContext(s => s.dispatch.hideConversation) + const hideConversation = ConvoState.useChatContext(s => s.dispatch.hideConversation) const onHideConv = () => { hideConversation(true) } - const onMuteConv = Chat.useChatContext(s => s.dispatch.mute) + const onMuteConv = ConvoState.useChatContext(s => s.dispatch.mute) const onUnhideConv = () => { hideConversation(false) } @@ -209,7 +210,7 @@ const InfoPanelMenuConnector = function InfoPanelMenuConnector(p: OwnProps) { ), } as const - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const hideItem = (() => { if (!conversationIDKey) { return null @@ -396,8 +397,8 @@ type AdhocHeaderProps = { } const AdhocHeader = (props: AdhocHeaderProps) => { - const meta = Chat.useChatContext(s => s.meta) - const participants = Chat.useChatContext(s => s.participants) + const meta = ConvoState.useChatContext(s => s.meta) + const participants = ConvoState.useChatContext(s => s.participants) const {channelHumans} = InfoPanelCommon.useHumans(participants, meta) return ( diff --git a/shared/chat/conversation/info-panel/settings/index.tsx b/shared/chat/conversation/info-panel/settings/index.tsx index 15c6c739bc64..dddc99e33694 100644 --- a/shared/chat/conversation/info-panel/settings/index.tsx +++ b/shared/chat/conversation/info-panel/settings/index.tsx @@ -1,5 +1,7 @@ import * as C from '@/constants' +import {isAssertion} from '@/constants/chat/helpers' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as Teams from '@/stores/teams' import * as T from '@/constants/types' @@ -14,7 +16,7 @@ type SettingsPanelProps = {isPreview: boolean} const SettingsPanel = (props: SettingsPanelProps) => { const {isPreview} = props const username = useCurrentUserState(s => s.username) - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const {status, teamname, teamType, channelname, teamID} = meta const yourOperations = Teams.useTeamsState(s => (teamname ? Teams.getCanPerformByID(s, teamID) : undefined)) const ignored = status === T.RPCChat.ConversationStatus.ignored @@ -33,17 +35,17 @@ const SettingsPanel = (props: SettingsPanelProps) => { } const teamMembers = Teams.useTeamsState(s => s.teamIDToMembers.get(teamID)) - const participantInfo = Chat.useChatContext(s => s.participants) + const participantInfo = ConvoState.useChatContext(s => s.participants) const membersForBlock = (teamMembers?.size ? [...teamMembers.keys()] : participantInfo.name).filter( - u => u !== username && !Chat.isAssertion(u) + u => u !== username && !isAssertion(u) ) - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onShowClearConversationDialog = () => { navigateAppend(conversationIDKey => ({name: 'chatDeleteHistoryWarning', params: {conversationIDKey}})) } - const hideConversation = Chat.useChatContext(s => s.dispatch.hideConversation) + const hideConversation = ConvoState.useChatContext(s => s.dispatch.hideConversation) const onHideConv = () => hideConversation(true) const onUnhideConv = () => hideConversation(false) const onShowBlockConversationDialog = () => { @@ -62,7 +64,7 @@ const SettingsPanel = (props: SettingsPanelProps) => { } } - const leaveConversation = Chat.useChatContext(s => s.dispatch.leaveConversation) + const leaveConversation = ConvoState.useChatContext(s => s.dispatch.leaveConversation) const onLeaveConversation = () => { leaveConversation() } @@ -75,7 +77,7 @@ const SettingsPanel = (props: SettingsPanelProps) => { } const showDangerZone = canDeleteHistory || entityType === 'adhoc' || entityType !== 'channel' - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) return ( { - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const {teamname, minWriterRole} = meta const canPerform = Teams.useTeamsState(s => (teamname ? Teams.getCanPerform(s, teamname) : undefined)) @@ -18,7 +18,7 @@ const MinWriterRole = () => { const [saving, setSaving] = React.useState(false) const [selected, setSelected] = React.useState(minWriterRole) - const setMinWriterRole = Chat.useChatContext(s => s.dispatch.setMinWriterRole) + const setMinWriterRole = ConvoState.useChatContext(s => s.dispatch.setMinWriterRole) const onSetNewRole = (role: T.Teams.TeamRoleType) => setMinWriterRole(role) const selectRole = (role: T.Teams.TeamRoleType) => { diff --git a/shared/chat/conversation/info-panel/settings/notifications.tsx b/shared/chat/conversation/info-panel/settings/notifications.tsx index ee8f9b909507..2490d81707a7 100644 --- a/shared/chat/conversation/info-panel/settings/notifications.tsx +++ b/shared/chat/conversation/info-panel/settings/notifications.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as React from 'react' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' @@ -101,14 +101,14 @@ const UnmutedNotificationPrefs = (props: UnmutedProps) => { } const Notifications = () => { - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const [channelWide, setChannelWide] = React.useState(meta.notificationsGlobalIgnoreMentions) const [desktop, setDesktop] = React.useState(meta.notificationsDesktop) const [mobile, setMobile] = React.useState(meta.notificationsMobile) const [muted, setMuted] = React.useState(meta.isMuted) const [saving, setSaving] = React.useState(false) const delayUnsave = Kb.useTimeout(() => setSaving(false), 100) - const updateNotificationSettings = Chat.useChatContext(s => s.dispatch.updateNotificationSettings) + const updateNotificationSettings = ConvoState.useChatContext(s => s.dispatch.updateNotificationSettings) const saveNotifications = ( desktop: T.Chat.NotificationsType, mobile: T.Chat.NotificationsType, @@ -118,7 +118,7 @@ const Notifications = () => { updateNotificationSettings(desktop, mobile, channelWide) delayUnsave() } - const mute = Chat.useChatContext(s => s.dispatch.mute) + const mute = ConvoState.useChatContext(s => s.dispatch.mute) const saveMuted = (muted: boolean) => { setSaving(true) mute(muted) diff --git a/shared/chat/conversation/input-area/container.tsx b/shared/chat/conversation/input-area/container.tsx index 4482b2915fb3..75e693925316 100644 --- a/shared/chat/conversation/input-area/container.tsx +++ b/shared/chat/conversation/input-area/container.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import {PerfProfiler} from '@/perf/react-profiler' import Normal from './normal' import Preview from './preview' @@ -7,9 +8,9 @@ import ThreadSearch from '../search' import {useThreadSearchRoute} from '../thread-search-route' const InputAreaContainer = () => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const showThreadSearch = !!useThreadSearchRoute() - const {membershipType, resetParticipants, wasFinalizedBy} = Chat.useChatContext( + const {membershipType, resetParticipants, wasFinalizedBy} = ConvoState.useChatContext( C.useShallow(s => { const {membershipType, resetParticipants, wasFinalizedBy} = s.meta return {membershipType, resetParticipants, wasFinalizedBy} diff --git a/shared/chat/conversation/input-area/location-popup.native.tsx b/shared/chat/conversation/input-area/location-popup.native.tsx index dd6d1e4cc65e..60cc11be84e9 100644 --- a/shared/chat/conversation/input-area/location-popup.native.tsx +++ b/shared/chat/conversation/input-area/location-popup.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as React from 'react' import {useConfigState} from '@/stores/config' import logger from '@/logger' @@ -11,7 +11,13 @@ import {requestLocationPermission} from '@/util/platform-specific' import * as ExpoLocation from 'expo-location' import {ignorePromise} from '@/constants/utils' -const LocationButton = (props: {disabled: boolean; label: string; onClick: () => void; subLabel?: string; primary?: boolean}) => ( +const LocationButton = (props: { + disabled: boolean + label: string + onClick: () => void + subLabel?: string + primary?: boolean +}) => ( style={styles.liveButton} > - {props.label} - {!!props.subLabel && {props.subLabel}} + + {props.label} + + {!!props.subLabel && ( + + {props.subLabel} + + )} ) @@ -39,7 +54,7 @@ const useWatchPosition = ( conversationIDKey: T.Chat.ConversationIDKey, setLocation: React.Dispatch> ) => { - const setCommandStatusInfo = Chat.useChatUIContext(s => s.dispatch.setCommandStatusInfo) + const setCommandStatusInfo = ConvoState.useChatUIContext(s => s.dispatch.setCommandStatusInfo) React.useEffect(() => { let unsub = () => {} logger.info('[location] perms check due to map') @@ -78,11 +93,11 @@ const useWatchPosition = ( } const LocationPopup = () => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const username = useCurrentUserState(s => s.username) const httpSrv = useConfigState(s => s.httpSrv) const [location, setLocation] = React.useState() - const locationDenied = Chat.useChatUIContext( + const locationDenied = ConvoState.useChatUIContext( s => s.commandStatus?.displayType === T.RPCChat.UICommandStatusDisplayTyp.error ) const [mapLoaded, setMapLoaded] = React.useState(false) @@ -91,7 +106,7 @@ const LocationPopup = () => { clearModals() } const onSettings = useConfigState(s => s.dispatch.defer.openAppSettings) - const sendMessage = Chat.useChatContext(s => s.dispatch.sendMessage) + const sendMessage = ConvoState.useChatContext(s => s.dispatch.sendMessage) const onLocationShare = (duration: string) => { onClose() sendMessage(duration ? `/location live ${duration}` : '/location') @@ -120,12 +135,33 @@ const LocationPopup = () => { setMapLoaded(true)} /> )} - - onLocationShare('15m')} subLabel="Live location" /> - onLocationShare('1h')} subLabel="Live location" /> - onLocationShare('8h')} subLabel="Live location" /> - onLocationShare('')} subLabel={mapLoaded ? `Accurate to ${location ? location.accuracy : 0} meters` : undefined} primary={true} /> - + + onLocationShare('15m')} + subLabel="Live location" + /> + onLocationShare('1h')} + subLabel="Live location" + /> + onLocationShare('8h')} + subLabel="Live location" + /> + onLocationShare('')} + subLabel={mapLoaded ? `Accurate to ${location ? location.accuracy : 0} meters` : undefined} + primary={true} + /> + ) diff --git a/shared/chat/conversation/input-area/normal/index.tsx b/shared/chat/conversation/input-area/normal/index.tsx index d61efe95e241..171da7ec3a7c 100644 --- a/shared/chat/conversation/input-area/normal/index.tsx +++ b/shared/chat/conversation/input-area/normal/index.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as React from 'react' import CommandMarkdown from '../../command-markdown' @@ -25,8 +26,8 @@ const useHintText = (p: { }) => { const {minWriterRole, isExploding, isEditing, cannotWrite} = p const username = useCurrentUserState(s => s.username) - const {teamType, teamname, channelname} = Chat.useChatContext(s => s.meta) - const participantInfoName = Chat.useChatContext(s => s.participants.name) + const {teamType, teamname, channelname} = ConvoState.useChatContext(s => s.meta) + const participantInfoName = ConvoState.useChatContext(s => s.participants.name) if (Kb.Styles.isMobile && isExploding) { return C.isLargeScreen ? `Write an exploding message` : 'Exploding message' } @@ -69,11 +70,11 @@ const useHintText = (p: { } const Input = function Input() { - const showGiphySearch = Chat.useChatUIContext(s => s.giphyWindow) - const showCommandMarkdown = Chat.useChatContext(s => !!s.commandMarkdown) - const showCommandStatus = Chat.useChatUIContext(s => !!s.commandStatus) - const replyTo = Chat.useChatUIContext(s => s.replyTo) - const showReplyTo = Chat.useChatContext(s => !!s.messageMap.get(replyTo)?.id) + const showGiphySearch = ConvoState.useChatUIContext(s => s.giphyWindow) + const showCommandMarkdown = ConvoState.useChatContext(s => !!s.commandMarkdown) + const showCommandStatus = ConvoState.useChatUIContext(s => !!s.commandStatus) + const replyTo = ConvoState.useChatUIContext(s => s.replyTo) + const showReplyTo = ConvoState.useChatContext(s => !!s.messageMap.get(replyTo)?.id) return ( {showReplyTo && } @@ -113,14 +114,14 @@ const ConnectedPlatformInput = function ConnectedPlatformInput() { const route = useRoute | RootRouteProps<'chatRoot'>>() const params = getRouteParamsFromRoute<'chatConversation' | 'chatRoot'>(route) const infoPanelShowing = !!(params && typeof params === 'object' && 'infoPanel' in params && params.infoPanel) - const uiData = Chat.useChatUIContext( + const uiData = ConvoState.useChatUIContext( C.useShallow(s => ({ editOrdinal: s.editing, replyTo: s.replyTo, unsentText: s.unsentText, })) ) - const data = Chat.useChatContext( + const data = ConvoState.useChatContext( C.useShallow(s => { const {meta, id: conversationIDKey, messageMap} = s const {sendMessage, jumpToRecent, setExplodingMode} = s.dispatch @@ -149,8 +150,8 @@ const ConnectedPlatformInput = function ConnectedPlatformInput() { const {suggestBotCommandsUpdateStatus, showReplyPreview} = data const {editOrdinal, unsentText} = uiData const isEditing = !!editOrdinal - const setEditing = Chat.useChatUIContext(s => s.dispatch.setEditing) - const updateUnsentText = Chat.useChatUIContext(s => s.dispatch.injectIntoInput) + const setEditing = ConvoState.useChatUIContext(s => s.dispatch.setEditing) + const updateUnsentText = ConvoState.useChatUIContext(s => s.dispatch.injectIntoInput) const [explodingModeSeconds, setExplodingModeSeconds] = React.useState(explodingModeSecondsRaw) const isExploding = explodingModeSeconds !== 0 @@ -177,7 +178,7 @@ const ConnectedPlatformInput = function ConnectedPlatformInput() { if (!text) return injectText('', true) sendMessage(text) - const cs = Chat.getConvoState(conversationIDKey) + const cs = ConvoState.getConvoState(conversationIDKey) if (cs.messageCenterOrdinal) { cs.dispatch.toggleThreadSearch(true) jumpToRecent() @@ -227,7 +228,7 @@ const ConnectedPlatformInput = function ConnectedPlatformInput() { React.useEffect(() => { const rows = [loadIDOnUnloadRef.current] return () => { - Chat.useChatState.getState().dispatch.unboxRows(rows) + ConvoState.unboxRows(rows) } }, [loadIDOnUnloadRef]) diff --git a/shared/chat/conversation/input-area/normal/input.desktop.tsx b/shared/chat/conversation/input-area/normal/input.desktop.tsx index 79fa47cea087..f8ac1c093399 100644 --- a/shared/chat/conversation/input-area/normal/input.desktop.tsx +++ b/shared/chat/conversation/input-area/normal/input.desktop.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' @@ -112,8 +112,7 @@ export function Input(p: InputLowLevelProps) { if (rowsMax) { heightStyles.maxHeight = - rowsMax * - (textStyle.lineHeight === undefined ? 20 : maybeParseInt(textStyle.lineHeight, 10) || 20) + rowsMax * (textStyle.lineHeight === undefined ? 20 : maybeParseInt(textStyle.lineHeight, 10) || 20) } const paddingStyles = padding ? Kb.Styles.padding(Kb.Styles.globalMargins[padding]) : {} @@ -312,7 +311,7 @@ const EmojiButton = function EmojiButton(p: EmojiButtonProps) { } const GiphyButton = function GiphyButton() { - const toggleGiphyPrefill = Chat.useChatUIContext(s => s.dispatch.toggleGiphyPrefill) + const toggleGiphyPrefill = ConvoState.useChatUIContext(s => s.dispatch.toggleGiphyPrefill) const onGiphyToggle = toggleGiphyPrefill return ( @@ -326,12 +325,10 @@ const fileListToPaths = (f: FileList): Array => { return Array.from(f).map(f => getPathForFile?.(f) ?? '') } -const FileButton = function FileButton(p: { - setHtmlInputRef: (i: HTMLInputElement | null) => void -}) { +const FileButton = function FileButton(p: {setHtmlInputRef: (i: HTMLInputElement | null) => void}) { const {setHtmlInputRef} = p const htmlInputRef = React.useRef(null) - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const pickFile = () => { const paths = htmlInputRef.current?.files ? fileListToPaths(htmlInputRef.current.files) : undefined const pathAndOutboxIDs = paths?.reduce>((arr, path: string) => { @@ -389,7 +386,7 @@ const useKeyboard = (p: UseKeyboardProps) => { const {htmlInputRef, focusInput, isEditing, onKeyDown, onCancelEditing} = p const {onChangeText, onEditLastMessage, showReplyPreview} = p const lastText = React.useRef('') - const setReplyTo = Chat.useChatUIContext(s => s.dispatch.setReplyTo) + const setReplyTo = ConvoState.useChatUIContext(s => s.dispatch.setReplyTo) const {scrollDown, scrollUp} = React.useContext(ScrollContext) const onCancelReply = () => { setReplyTo(T.Chat.numberToOrdinal(0)) @@ -518,7 +515,7 @@ const PlatformInput = function PlatformInput(p: Props) { const focusInput = () => { inputRef.current?.focus() } - const setEditing = Chat.useChatUIContext(s => s.dispatch.setEditing) + const setEditing = ConvoState.useChatUIContext(s => s.dispatch.setEditing) const onEditLastMessage = () => { setEditing('last') } diff --git a/shared/chat/conversation/input-area/normal/input.native.tsx b/shared/chat/conversation/input-area/normal/input.native.tsx index 39d3bac7e964..6bfdadc638cc 100644 --- a/shared/chat/conversation/input-area/normal/input.native.tsx +++ b/shared/chat/conversation/input-area/normal/input.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as React from 'react' import AudioRecorder from '@/chat/audio/audio-recorder.native' @@ -235,7 +235,7 @@ const Buttons = function Buttons(p: ButtonsProps) { updatePickerMap(pickKey, undefined) }, [emojiStr, insertText, lastEmoji, updatePickerMap]) - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const openEmojiPicker = () => { navigateAppend(conversationIDKey => ({ name: 'chatChooseEmoji', @@ -351,9 +351,9 @@ type ChatFilePickerProps = { } const ChatFilePicker = (p: ChatFilePickerProps) => { const {attachTo, showingPopup, hidePopup} = p - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const filePickerError = useConfigState(s => s.dispatch.filePickerError) - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const launchNativeImagePicker = (mediaType: 'photo' | 'video' | 'mixed' | 'file', location: string) => { const f = async () => { const handleSelection = (result: ImagePicker.ImagePickerResult) => { @@ -576,7 +576,7 @@ const PlatformInput = (p: Props) => { ourShowMenu('exploding') } - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onPasteImage = (uri: Array) => { try { const pathAndOutboxIDs = uri.map(path => ({path})) diff --git a/shared/chat/conversation/input-area/normal/moremenu-popup.native.tsx b/shared/chat/conversation/input-area/normal/moremenu-popup.native.tsx index 5081785b30b5..8ad1b4eae3a8 100644 --- a/shared/chat/conversation/input-area/normal/moremenu-popup.native.tsx +++ b/shared/chat/conversation/input-area/normal/moremenu-popup.native.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' type Props = { @@ -8,8 +8,8 @@ type Props = { const MoreMenuPopup = (props: Props) => { const {onHidden, visible} = props - const injectIntoInput = Chat.useChatUIContext(s => s.dispatch.injectIntoInput) - const navigateAppend = Chat.useChatNavigateAppend() + const injectIntoInput = ConvoState.useChatUIContext(s => s.dispatch.injectIntoInput) + const navigateAppend = ConvoState.useChatNavigateAppend() const onLocationShare = () => { navigateAppend(conversationIDKey => ({name: 'chatLocationPreview', params: {conversationIDKey}})) } diff --git a/shared/chat/conversation/input-area/normal/set-explode-popup/hooks.tsx b/shared/chat/conversation/input-area/normal/set-explode-popup/hooks.tsx index 128a336397a0..8ffbeb7acd5e 100644 --- a/shared/chat/conversation/input-area/normal/set-explode-popup/hooks.tsx +++ b/shared/chat/conversation/input-area/normal/set-explode-popup/hooks.tsx @@ -1,4 +1,5 @@ import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import type {Props} from '.' @@ -31,8 +32,8 @@ const makeItems = (meta: T.Chat.ConversationMeta) => { export default (p: Props) => { const {setExplodingMode, onHidden, visible, attachTo, onAfterSelect} = p - const _meta = Chat.useChatContext(s => s.meta) - const selected = Chat.useChatContext(s => s.explodingMode) + const _meta = ConvoState.useChatContext(s => s.meta) + const selected = ConvoState.useChatContext(s => s.explodingMode) const onSelect = (seconds: number) => { setTimeout(() => { setExplodingMode(seconds) diff --git a/shared/chat/conversation/input-area/normal/typing.tsx b/shared/chat/conversation/input-area/normal/typing.tsx index d1031d8ee82d..23d97cb2a21f 100644 --- a/shared/chat/conversation/input-area/normal/typing.tsx +++ b/shared/chat/conversation/input-area/normal/typing.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' const Names = (props: {names?: ReadonlySet}) => { @@ -44,8 +44,8 @@ const Names = (props: {names?: ReadonlySet}) => { const emptySet = new Set() const Typing = function Typing() { - const showGiphySearch = Chat.useChatUIContext(s => s.giphyWindow) - const names = Chat.useChatContext( + const showGiphySearch = ConvoState.useChatUIContext(s => s.giphyWindow) + const names = ConvoState.useChatContext( C.useShallow(s => { const names = s.typing if (!C.isMobile) return names diff --git a/shared/chat/conversation/input-area/preview.tsx b/shared/chat/conversation/input-area/preview.tsx index 3bf3c4e9756c..a3471dadff17 100644 --- a/shared/chat/conversation/input-area/preview.tsx +++ b/shared/chat/conversation/input-area/preview.tsx @@ -1,11 +1,11 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as React from 'react' import * as Kb from '@/common-adapters' const Preview = () => { - const meta = Chat.useChatContext(s => s.meta) - const onJoinChannel = Chat.useChatContext(s => s.dispatch.joinConversation) - const onLeaveChannel = Chat.useChatContext(s => s.dispatch.leaveConversation) + const meta = ConvoState.useChatContext(s => s.meta) + const onJoinChannel = ConvoState.useChatContext(s => s.dispatch.joinConversation) + const onLeaveChannel = ConvoState.useChatContext(s => s.dispatch.leaveConversation) const {channelname} = meta const [clicked, setClicked] = React.useState(undefined) diff --git a/shared/chat/conversation/input-area/suggestors/channels.tsx b/shared/chat/conversation/input-area/suggestors/channels.tsx index ef5ab6dc78a8..334347140aa4 100644 --- a/shared/chat/conversation/input-area/suggestors/channels.tsx +++ b/shared/chat/conversation/input-area/suggestors/channels.tsx @@ -1,5 +1,7 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' +import type {ConvoState as ConvoStateType} from '@/stores/convostate' import * as T from '@/constants/types' import * as Teams from '@/stores/teams' import * as Common from './common' @@ -42,7 +44,7 @@ const ItemRenderer = (p: Common.ItemRendererProps) => { const noChannel: Array<{channelname: string}> = [] const getChannelSuggestions = ( - s: Chat.ConvoState, + s: ConvoStateType, teamname: string, teamMeta: Teams.State['teamMeta'] ) => { @@ -79,8 +81,8 @@ const getChannelSuggestions = ( } const useDataSource = (filter: string) => { - const conversationIDKey = Chat.useChatContext(s => s.id) - const meta = Chat.useChatContext(s => s.meta) + const conversationIDKey = ConvoState.useChatContext(s => s.id) + const meta = ConvoState.useChatContext(s => s.meta) const {teamID} = meta const suggestChannelsLoading = C.Waiting.useAnyWaiting([ @@ -88,7 +90,7 @@ const useDataSource = (filter: string) => { C.waitingKeyChatMutualTeams(conversationIDKey), ]) const teamMeta = Teams.useTeamsState(s => s.teamMeta) - return Chat.useChatContext( + return ConvoState.useChatContext( C.useDeep(s => { const fil = filter.toLowerCase() // don't include 'small' here to ditch the single #general suggestion diff --git a/shared/chat/conversation/input-area/suggestors/commands.tsx b/shared/chat/conversation/input-area/suggestors/commands.tsx index 7b583f0ef240..4c07743aeee3 100644 --- a/shared/chat/conversation/input-area/suggestors/commands.tsx +++ b/shared/chat/conversation/input-area/suggestors/commands.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Common from './common' import * as Kb from '@/common-adapters' @@ -43,8 +44,8 @@ const blankCommands: Array = [] const ItemRenderer = (p: Common.ItemRendererProps) => { const {selected, item: command} = p const prefix = getCommandPrefix(command) - const botSettings = Chat.useChatContext(s => s.botSettings) - const enabled = Chat.useChatContext(s => { + const botSettings = ConvoState.useChatContext(s => s.botSettings) + const enabled = ConvoState.useChatContext(s => { const {botCommands} = s.meta const suggestBotCommands = botCommands.typ === T.RPCChat.ConversationCommandGroupsTyp.custom @@ -106,9 +107,9 @@ type UseDataSourceProps = { const useDataSource = (p: UseDataSourceProps) => { const {filter, inputRef, lastTextRef} = p const staticConfig = Chat.useChatState(s => s.staticConfig) - const showGiphySearch = Chat.useChatUIContext(s => s.giphyWindow) - const showCommandMarkdown = Chat.useChatContext(s => !!s.commandMarkdown) - return Chat.useChatContext( + const showGiphySearch = ConvoState.useChatUIContext(s => s.giphyWindow) + const showCommandMarkdown = ConvoState.useChatContext(s => !!s.commandMarkdown) + return ConvoState.useChatContext( C.useShallow(s => { if (showCommandMarkdown || showGiphySearch) { return [] diff --git a/shared/chat/conversation/input-area/suggestors/emoji.tsx b/shared/chat/conversation/input-area/suggestors/emoji.tsx index 2c427f4ee24b..efa61901057d 100644 --- a/shared/chat/conversation/input-area/suggestors/emoji.tsx +++ b/shared/chat/conversation/input-area/suggestors/emoji.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Common from './common' import * as Kb from '@/common-adapters' import {type EmojiData, RPCToEmojiData, emojiData} from '@/common-adapters/emoji' @@ -38,7 +38,7 @@ const emojiPrepass = /[a-z0-9_]{2,}(?!.*:)/i const empty = new Array() const useDataSource = (filter: string) => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const {emojis: userEmojis, loading: userEmojisLoading} = useUserEmoji({conversationIDKey}) if (!emojiPrepass.test(filter)) { diff --git a/shared/chat/conversation/input-area/suggestors/index.tsx b/shared/chat/conversation/input-area/suggestors/index.tsx index ea1c438ff82d..3ae75eaeaefa 100644 --- a/shared/chat/conversation/input-area/suggestors/index.tsx +++ b/shared/chat/conversation/input-area/suggestors/index.tsx @@ -1,5 +1,5 @@ import * as Channels from './channels' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Commands from './commands' import * as Emoji from './emoji' import * as Kb from '@/common-adapters' @@ -392,7 +392,7 @@ type PopupProps = { } const Popup = (p: PopupProps) => { const {children, suggestionOverlayStyle, setInactive, inputRef} = p - const conversationIdKey = Chat.useChatContext(s => s.id) + const conversationIdKey = ConvoState.useChatContext(s => s.id) const attachRef = inputRef as React.RefObject @@ -409,9 +409,9 @@ const Popup = (p: PopupProps) => { style={suggestionOverlayStyle} > {Kb.Styles.isMobile ? ( - + {children} - + ) : ( children )} diff --git a/shared/chat/conversation/input-area/suggestors/users.tsx b/shared/chat/conversation/input-area/suggestors/users.tsx index e0319a81dcbb..2e7c24840e85 100644 --- a/shared/chat/conversation/input-area/suggestors/users.tsx +++ b/shared/chat/conversation/input-area/suggestors/users.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Common from './common' import * as Kb from '@/common-adapters' @@ -139,7 +140,7 @@ const getTeams = (layout?: T.RPCChat.UIInboxLayout) => { const useDataUsers = () => { const infoMap = useUsersState(s => s.infoMap) - const {participantInfo, teamID, teamType} = Chat.useChatContext( + const {participantInfo, teamID, teamType} = ConvoState.useChatContext( C.useShallow(s => ({participantInfo: s.participants, teamID: s.meta.teamID, teamType: s.meta.teamType})) ) const teamMembers = useTeamMembers(teamID) diff --git a/shared/chat/conversation/list-area/hooks.tsx b/shared/chat/conversation/list-area/hooks.tsx index 7001595ee7d1..dc9be90b2997 100644 --- a/shared/chat/conversation/list-area/hooks.tsx +++ b/shared/chat/conversation/list-area/hooks.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import JumpToRecent from './jump-to-recent' import type * as T from '@/constants/types' import logger from '@/logger' @@ -13,14 +14,14 @@ export const useActions = (p: {conversationIDKey: T.Chat.ConversationIDKey}) => return } // Force mark as read since this is triggered by navigation (user action) - Chat.getConvoState(conversationIDKey).dispatch.markThreadAsRead(true) + ConvoState.getConvoState(conversationIDKey).dispatch.markThreadAsRead(true) } return {markInitiallyLoadedThreadAsRead} } export const useJumpToRecent = (scrollToBottom: () => void, numOrdinals: number) => { - const data = Chat.useChatContext( + const data = ConvoState.useChatContext( C.useShallow(s => { const {loaded, moreToLoadForward} = s const {jumpToRecent, toggleThreadSearch} = s.dispatch diff --git a/shared/chat/conversation/list-area/index.desktop.tsx b/shared/chat/conversation/list-area/index.desktop.tsx index ba9d3df80fb6..1a9ca47bfaf1 100644 --- a/shared/chat/conversation/list-area/index.desktop.tsx +++ b/shared/chat/conversation/list-area/index.desktop.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as Hooks from './hooks' import * as React from 'react' @@ -35,7 +35,7 @@ const useScrolling = (p: { setListRef: (r: HTMLDivElement | null) => void centeredOrdinal: T.Chat.Ordinal | undefined }) => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const {listRef, setListRef: _setListRef, containsLatestMessage} = p const containsLatestMessageRef = React.useRef(containsLatestMessage) React.useEffect(() => { @@ -43,17 +43,14 @@ const useScrolling = (p: { }, [containsLatestMessage]) const {messageOrdinals, centeredOrdinal, loaded} = p const numOrdinals = messageOrdinals.length - const loadNewerMessagesDueToScroll = Chat.useChatContext(s => s.dispatch.loadNewerMessagesDueToScroll) - const loadNewerMessages = C.useThrottledCallback( - () => { - loadNewerMessagesDueToScroll(numOrdinals) - }, - 200 - ) + const loadNewerMessagesDueToScroll = ConvoState.useChatContext(s => s.dispatch.loadNewerMessagesDueToScroll) + const loadNewerMessages = C.useThrottledCallback(() => { + loadNewerMessagesDueToScroll(numOrdinals) + }, 200) // if we scroll up try and keep the position const scrollBottomOffsetRef = React.useRef(undefined) - const loadOlderMessages = Chat.useChatContext(s => s.dispatch.loadOlderMessagesDueToScroll) + const loadOlderMessages = ConvoState.useChatContext(s => s.dispatch.loadOlderMessagesDueToScroll) const {markInitiallyLoadedThreadAsRead} = Hooks.useActions({conversationIDKey}) // pixels away from top/bottom to load/be locked const listEdgeSlopBottom = 10 @@ -338,7 +335,7 @@ const useScrolling = (p: { }, [scrollDown, scrollToBottom, scrollUp, setScrollRef]) // go to editing message - const editingOrdinal = Chat.useChatUIContext(s => s.editing) + const editingOrdinal = ConvoState.useChatUIContext(s => s.editing) const lastEditingOrdinalRef = React.useRef(0) React.useEffect(() => { if (lastEditingOrdinalRef.current === editingOrdinal) return @@ -386,10 +383,7 @@ const useItems = (p: { )} > - + ) } @@ -475,8 +469,8 @@ const useItems = (p: { const noOrdinals = new Array() const ThreadWrapper = function ThreadWrapper() { - const editingOrdinal = Chat.useChatUIContext(s => s.editing) - const data = Chat.useChatContext( + const editingOrdinal = ConvoState.useChatUIContext(s => s.editing) + const data = ConvoState.useChatContext( C.useShallow(s => { const {id: conversationIDKey} = s const {messageCenterOrdinal: mco, messageOrdinals = noOrdinals, loaded} = s diff --git a/shared/chat/conversation/list-area/index.native.tsx b/shared/chat/conversation/list-area/index.native.tsx index 52d94423a5b2..62b4c6706133 100644 --- a/shared/chat/conversation/list-area/index.native.tsx +++ b/shared/chat/conversation/list-area/index.native.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Hooks from './hooks' import * as Kb from '@/common-adapters' @@ -42,7 +42,7 @@ const useScrolling = (p: { }) => { const {listRef, centeredOrdinal, messageOrdinals} = p const numOrdinals = messageOrdinals.length - const loadOlderMessages = Chat.useChatContext(s => s.dispatch.loadOlderMessagesDueToScroll) + const loadOlderMessages = ConvoState.useChatContext(s => s.dispatch.loadOlderMessagesDueToScroll) const [scrollToBottom] = React.useState(() => () => { listRef.current?.scrollToOffset({animated: false, offset: 0}) }) @@ -102,7 +102,7 @@ const ConversationList = function ConversationList() { ) : null - const listData = Chat.useChatContext( + const listData = ConvoState.useChatContext( C.useShallow(s => { const {id: conversationIDKey, loaded, messageCenterOrdinal} = s const centeredOrdinal = messageCenterOrdinal?.ordinal ?? T.Chat.numberToOrdinal(-1) @@ -134,12 +134,7 @@ const ConversationList = function ConversationList() { if (!ordinal) { return null } - return ( - - ) + return } const numOrdinals = messageOrdinals.length @@ -149,7 +144,7 @@ const ConversationList = function ConversationList() { if (!ordinal) { return 'null' } - const convoState = Chat.getConvoState(conversationIDKey) + const convoState = ConvoState.getConvoState(conversationIDKey) return convoState.rowRecycleTypeMap.get(ordinal) ?? convoState.messageTypeMap.get(ordinal) ?? 'text' }, [conversationIDKey] diff --git a/shared/chat/conversation/load-status.tsx b/shared/chat/conversation/load-status.tsx index 38030e6b6078..87732a956fde 100644 --- a/shared/chat/conversation/load-status.tsx +++ b/shared/chat/conversation/load-status.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as React from 'react' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' @@ -31,8 +31,8 @@ const getBkgColor = (status: T.RPCChat.UIChatThreadStatusTyp) => { } const ThreadLoadStatus = () => { - const status = Chat.useChatContext(s => s.threadLoadStatus) - const conversationIDKey = Chat.useChatContext(s => s.id) + const status = ConvoState.useChatContext(s => s.threadLoadStatus) + const conversationIDKey = ConvoState.useChatContext(s => s.id) logger.info(`ThreadLoadStatus: convID: ${conversationIDKey} status: ${status}`) if (status === T.RPCChat.UIChatThreadStatusTyp.none) { diff --git a/shared/chat/conversation/messages/account-payment/container.tsx b/shared/chat/conversation/messages/account-payment/container.tsx index 9a2b95d34091..a5f7d2912c3d 100644 --- a/shared/chat/conversation/messages/account-payment/container.tsx +++ b/shared/chat/conversation/messages/account-payment/container.tsx @@ -1,4 +1,6 @@ import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' +import type {ConvoState as ConvoStateType} from '@/stores/convostate' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' import MarkdownMemo from '@/wallets/markdown-memo' @@ -45,7 +47,7 @@ type OwnProps = { } const getRequestMessageInfo = ( - accountsInfoMap: Chat.ConvoState['accountsInfoMap'], + accountsInfoMap: ConvoStateType['accountsInfoMap'], message: T.Chat.MessageRequestPayment ) => { const maybeRequestInfo = accountsInfoMap.get(message.id) @@ -62,7 +64,7 @@ const getRequestMessageInfo = ( const ConnectedAccountPayment = (ownProps: OwnProps) => { const you = useCurrentUserState(s => s.username) - const accountsInfoMap = Chat.useChatContext(s => s.accountsInfoMap) + const accountsInfoMap = ConvoState.useChatContext(s => s.accountsInfoMap) const stateProps = (() => { const youAreSender = ownProps.message.author === you diff --git a/shared/chat/conversation/messages/attachment/file.tsx b/shared/chat/conversation/messages/attachment/file.tsx index 223db9e84d87..38b7dad13bb9 100644 --- a/shared/chat/conversation/messages/attachment/file.tsx +++ b/shared/chat/conversation/messages/attachment/file.tsx @@ -1,6 +1,7 @@ import * as C from '@/constants' import * as CryptoRoutes from '@/constants/crypto' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import {isPathSaltpack, isPathSaltpackEncrypted, isPathSaltpackSigned} from '@/util/path' import captialize from 'lodash/capitalize' @@ -19,7 +20,7 @@ type OwnProps = { function FileContainer(p: OwnProps) { const {isEditing, message, ordinal} = p - const {attachmentDownload, messageAttachmentNativeShare} = Chat.useChatContext( + const {attachmentDownload, messageAttachmentNativeShare} = ConvoState.useChatContext( C.useShallow(s => ({ attachmentDownload: s.dispatch.attachmentDownload, messageAttachmentNativeShare: s.dispatch.messageAttachmentNativeShare, diff --git a/shared/chat/conversation/messages/attachment/image/index.tsx b/shared/chat/conversation/messages/attachment/image/index.tsx index 6f433768822b..c6dc0d99a80e 100644 --- a/shared/chat/conversation/messages/attachment/image/index.tsx +++ b/shared/chat/conversation/messages/attachment/image/index.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as React from 'react' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import ImageImpl from './imageimpl' import { @@ -23,7 +23,7 @@ type Props = { function Image(p: Props) { const {message, ordinal, showPopup} = p const {isCollapsed, title, transferProgress, transferState} = message - const attachmentPreviewSelect = Chat.useChatContext(s => s.dispatch.attachmentPreviewSelect) + const attachmentPreviewSelect = ConvoState.useChatContext(s => s.dispatch.attachmentPreviewSelect) const fileName = getAttachmentDisplayFileName(message) const showTitle = !!title const openFullscreen = () => { @@ -33,12 +33,13 @@ function Image(p: Props) { const containerStyle = styles.container const collapseIcon = useCollapseIcon(ordinal, isCollapsed, false) - const filename = Kb.Styles.isMobile || !fileName ? null : ( - - {fileName} - {collapseIcon} - - ) + const filename = + Kb.Styles.isMobile || !fileName ? null : ( + + {fileName} + {collapseIcon} + + ) const toastTargetRef = React.useRef(null) @@ -53,7 +54,7 @@ function Image(p: Props) { > : null} - + ) diff --git a/shared/chat/conversation/messages/attachment/shared.tsx b/shared/chat/conversation/messages/attachment/shared.tsx index 7d6f208f9687..317c20cd468b 100644 --- a/shared/chat/conversation/messages/attachment/shared.tsx +++ b/shared/chat/conversation/messages/attachment/shared.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import {clampImageSize} from '@/constants/chat/helpers' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' @@ -79,7 +80,7 @@ export const TransferIcon = (p: { default: } } - const download = Chat.useChatContext(s => + const download = ConvoState.useChatContext(s => C.isMobile ? s.dispatch.messageAttachmentNativeSave : s.dispatch.attachmentDownload ) const onDownload = () => { @@ -173,7 +174,7 @@ export const getAttachmentPreviewSize = ( ) => { const {fileURL, previewHeight, previewWidth} = message let {previewURL} = message - let {height, width} = Chat.clampImageSize(previewWidth, previewHeight, maxWidth, maxHeight) + let {height, width} = clampImageSize(previewWidth, previewHeight, maxWidth, maxHeight) // This is mostly a sanity check and also allows us to handle HEIC even though the go side doesn't // understand. if (useSquareFallback && (height === 0 || width === 0)) { @@ -234,7 +235,7 @@ const styles = Kb.Styles.styleSheetCreate(() => ({ })) const useCollapseAction = (ordinal: T.Chat.Ordinal) => { - const toggleMessageCollapse = Chat.useChatContext(s => s.dispatch.toggleMessageCollapse) + const toggleMessageCollapse = ConvoState.useChatContext(s => s.dispatch.toggleMessageCollapse) const onCollapse = () => { toggleMessageCollapse(T.Chat.numberToMessageID(T.Chat.ordinalToNumber(ordinal)), ordinal) } diff --git a/shared/chat/conversation/messages/attachment/video/index.tsx b/shared/chat/conversation/messages/attachment/video/index.tsx index 750be423ad16..835b7afcd1d6 100644 --- a/shared/chat/conversation/messages/attachment/video/index.tsx +++ b/shared/chat/conversation/messages/attachment/video/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as Kb from '@/common-adapters' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import VideoImpl from './videoimpl' import { @@ -23,7 +23,7 @@ type Props = { function Video(p: Props) { const {message, ordinal, showPopup} = p const {isCollapsed, submitState, title, transferProgress, transferState} = message - const attachmentPreviewSelect = Chat.useChatContext(s => s.dispatch.attachmentPreviewSelect) + const attachmentPreviewSelect = ConvoState.useChatContext(s => s.dispatch.attachmentPreviewSelect) const fileName = getAttachmentDisplayFileName(message) const showTitle = !!title const openFullscreen = () => { @@ -33,12 +33,13 @@ function Video(p: Props) { const containerStyle = styles.container const collapseIcon = useCollapseIcon(ordinal, isCollapsed, false) - const filename = Kb.Styles.isMobile || !fileName ? null : ( - - {fileName} - {collapseIcon} - - ) + const filename = + Kb.Styles.isMobile || !fileName ? null : ( + + {fileName} + {collapseIcon} + + ) const toastTargetRef = React.useRef(null) @@ -54,7 +55,7 @@ function Video(p: Props) { > : null} - + ) return ( - + {isCollapsed ? : content} ) diff --git a/shared/chat/conversation/messages/cards/make-team.tsx b/shared/chat/conversation/messages/cards/make-team.tsx index e01b118a7144..b76026f0a6b4 100644 --- a/shared/chat/conversation/messages/cards/make-team.tsx +++ b/shared/chat/conversation/messages/cards/make-team.tsx @@ -1,8 +1,8 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' const MakeTeam = () => { - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onShowNewTeamDialog = () => navigateAppend(conversationIDKey => ({name: 'chatShowNewTeamDialog', params: {conversationIDKey}})) return ( diff --git a/shared/chat/conversation/messages/cards/team-journey/container.tsx b/shared/chat/conversation/messages/cards/team-journey/container.tsx index d4cab52cc24d..2e5842867319 100644 --- a/shared/chat/conversation/messages/cards/team-journey/container.tsx +++ b/shared/chat/conversation/messages/cards/team-journey/container.tsx @@ -1,5 +1,7 @@ import * as C from '@/constants' +import {isBigTeam as getIsBigTeam} from '@/constants/chat/helpers' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' @@ -13,16 +15,16 @@ const emptyJourney = Chat.makeMessageJourneycard({}) const TeamJourneyConnected = (ownProps: OwnProps) => { const {ordinal} = ownProps - const m = Chat.useChatContext(s => s.messageMap.get(ordinal)) + const m = ConvoState.useChatContext(s => s.messageMap.get(ordinal)) const message = m?.type === 'journeycard' ? m : emptyJourney - const conv = Chat.useChatContext(s => s.meta) + const conv = ConvoState.useChatContext(s => s.meta) const {cannotWrite, channelname, teamname, teamID} = conv const welcomeMessage = {display: '', raw: '', set: false} const canShowcase = Teams.useTeamsState(s => Teams.canShowcase(s, teamID)) - const isBigTeam = Chat.useChatState(s => Chat.isBigTeam(s, teamID)) + const isBigTeam = Chat.useChatState(s => getIsBigTeam(s.inboxLayout, teamID)) const navigateAppend = C.Router2.navigateAppend const _onAuthorClick = (teamID: T.Teams.TeamID) => navigateAppend({name: 'team', params: {teamID}}) - const dismissJourneycard = Chat.useChatContext(s => s.dispatch.dismissJourneycard) + const dismissJourneycard = ConvoState.useChatContext(s => s.dispatch.dismissJourneycard) const _onDismiss = (cardType: T.RPCChat.JourneycardType, ordinal: T.Chat.Ordinal) => dismissJourneycard(cardType, ordinal) const previewConversation = C.Router2.previewConversation @@ -42,7 +44,7 @@ const TeamJourneyConnected = (ownProps: OwnProps) => { const onGoToChannel = (channelName: string) => _onGoToChannel(channelName, teamname) const onPublishTeam = () => _onPublishTeam(teamID) - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const {cardType} = message let textComponent: React.ReactNode let image: Kb.IconType | undefined diff --git a/shared/chat/conversation/messages/emoji-row.tsx b/shared/chat/conversation/messages/emoji-row.tsx index 85fe9b007486..cb4bfcc59a06 100644 --- a/shared/chat/conversation/messages/emoji-row.tsx +++ b/shared/chat/conversation/messages/emoji-row.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as React from 'react' import {useOrdinal} from './ids-context' import * as Kb from '@/common-adapters' @@ -30,10 +31,10 @@ const useTopReacjis = () => function EmojiRowContainer(p: OwnProps) { const {className, hasUnfurls, messageType, onReact: onReactProp, onReply: onReplyProp, onShowingEmojiPicker, style} = p const ordinal = useOrdinal() - const setReplyTo = Chat.useChatUIContext(s => s.dispatch.setReplyTo) - const toggleMessageReaction = Chat.useChatContext(s => s.dispatch.toggleMessageReaction) + const setReplyTo = ConvoState.useChatUIContext(s => s.dispatch.setReplyTo) + const toggleMessageReaction = ConvoState.useChatContext(s => s.dispatch.toggleMessageReaction) const emojis = useTopReacjis() - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const _onForward = () => { navigateAppend(conversationIDKey => ({ name: 'chatForwardMsgPick', diff --git a/shared/chat/conversation/messages/message-popup/attachment.tsx b/shared/chat/conversation/messages/message-popup/attachment.tsx index 25db9e4b9033..b5a6f15773ad 100644 --- a/shared/chat/conversation/messages/message-popup/attachment.tsx +++ b/shared/chat/conversation/messages/message-popup/attachment.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as React from 'react' import type * as T from '@/constants/types' import {type Position, fileUIName, type StylesCrossPlatform} from '@/styles' @@ -21,7 +22,7 @@ const emptyMessage = Chat.makeMessageAttachment({}) const PopAttach = (ownProps: OwnProps) => { const {ordinal, attachTo, mode, onHidden, position, style, visible} = ownProps - const message = Chat.useChatContext(s => { + const message = ConvoState.useChatContext(s => { const m = s.messageMap.get(ordinal) const message = m?.type === 'attachment' ? m : emptyMessage return message @@ -36,7 +37,7 @@ const PopAttach = (ownProps: OwnProps) => { messageAttachmentNativeSave, messageAttachmentNativeShare, showInfoPanel, - } = Chat.useChatContext( + } = ConvoState.useChatContext( C.useShallow(s => { const { attachmentDownload, diff --git a/shared/chat/conversation/messages/message-popup/hooks.tsx b/shared/chat/conversation/messages/message-popup/hooks.tsx index 1fef4cca632e..b261fede1208 100644 --- a/shared/chat/conversation/messages/message-popup/hooks.tsx +++ b/shared/chat/conversation/messages/message-popup/hooks.tsx @@ -1,6 +1,7 @@ import type * as T from '@/constants/types' import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import {useConfigState} from '@/stores/config' import {useCurrentUserState} from '@/stores/current-user' @@ -45,19 +46,19 @@ const getConversationLabel = ( } export const useItems = (ordinal: T.Chat.Ordinal, onHidden: () => void) => { - const message = Chat.useChatContext(s => { + const message = ConvoState.useChatContext(s => { return s.messageMap.get(ordinal) ?? emptyText }) const isAttach = message.type === 'attachment' const {author, id, deviceName, timestamp, deviceRevokedAt} = message - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const {teamID, teamname} = meta - const participantInfo = Chat.useChatContext(s => s.participants) - const toggleMessageReaction = Chat.useChatContext(s => s.dispatch.toggleMessageReaction) + const participantInfo = ConvoState.useChatContext(s => s.participants) + const toggleMessageReaction = ConvoState.useChatContext(s => s.dispatch.toggleMessageReaction) const onReact = (emoji: string) => { toggleMessageReaction(ordinal, emoji) } - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const _onAddReaction = () => { navigateAppend(conversationIDKey => ({ name: 'chatChooseEmoji', @@ -108,13 +109,13 @@ export const useItems = (ordinal: T.Chat.Ordinal, onHidden: () => void) => { {icon: 'iconfont-link', onClick: onCopyLink, title: 'Copy a link to this message'}, ] as const - const {messageDelete, pinMessage, setMarkAsUnread} = Chat.useChatContext( + const {messageDelete, pinMessage, setMarkAsUnread} = ConvoState.useChatContext( C.useShallow(s => { const {messageDelete, pinMessage, setMarkAsUnread} = s.dispatch return {messageDelete, pinMessage, setMarkAsUnread} }) ) - const {setEditing, setReplyTo} = Chat.useChatUIContext( + const {setEditing, setReplyTo} = ConvoState.useChatUIContext( C.useShallow(s => ({setEditing: s.dispatch.setEditing, setReplyTo: s.dispatch.setReplyTo})) ) @@ -269,7 +270,7 @@ export const useItems = (ordinal: T.Chat.Ordinal, onHidden: () => void) => { } export const useHeader = (ordinal: T.Chat.Ordinal, onHidden: () => void) => { - const message = Chat.useChatContext(s => { + const message = ConvoState.useChatContext(s => { return s.messageMap.get(ordinal) ?? emptyText }) const you = useCurrentUserState(s => s.username) diff --git a/shared/chat/conversation/messages/message-popup/index.tsx b/shared/chat/conversation/messages/message-popup/index.tsx index dd46149379c6..98a08bc6b072 100644 --- a/shared/chat/conversation/messages/message-popup/index.tsx +++ b/shared/chat/conversation/messages/message-popup/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import type * as React from 'react' import AttachmentMessage from './attachment' @@ -19,7 +19,7 @@ type Props = { function MessagePopup(p: Props) { const {ordinal, attachTo, onHidden, position, style, visible, mode} = p - const type = Chat.useChatContext(s => s.messageMap.get(ordinal)?.type) + const type = ConvoState.useChatContext(s => s.messageMap.get(ordinal)?.type) switch (type) { case 'text': case 'setChannelname': @@ -118,12 +118,12 @@ export const useMessagePopup = (p: { shouldShow?: () => boolean style?: Kb.Styles.StylesCrossPlatform }) => { - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const {ordinal, shouldShow, style} = p const makePopup = (p: Kb.Popup2Parms) => { const {attachTo, hidePopup} = p return (shouldShow?.() ?? true) ? ( - + - + ) : null } return Kb.usePopup2(makePopup) diff --git a/shared/chat/conversation/messages/message-popup/journeycard.tsx b/shared/chat/conversation/messages/message-popup/journeycard.tsx index 3578f288a69f..b015e022262e 100644 --- a/shared/chat/conversation/messages/message-popup/journeycard.tsx +++ b/shared/chat/conversation/messages/message-popup/journeycard.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import type * as React from 'react' @@ -16,11 +16,11 @@ type OwnProps = { const JourneyCard = (ownProps: OwnProps) => { const {ordinal, attachTo, mode, onHidden, style, visible, position} = ownProps - const cardType = Chat.useChatContext( + const cardType = ConvoState.useChatContext( s => s.messageMap.get(ordinal)?.cardType ?? T.RPCChat.JourneycardType.unused ) - const dismissJourneycard = Chat.useChatContext(s => s.dispatch.dismissJourneycard) + const dismissJourneycard = ConvoState.useChatContext(s => s.dispatch.dismissJourneycard) const onDismiss = () => { dismissJourneycard(cardType, ordinal) } diff --git a/shared/chat/conversation/messages/message-popup/text.tsx b/shared/chat/conversation/messages/message-popup/text.tsx index f822aacc8331..340bbc6dbbef 100644 --- a/shared/chat/conversation/messages/message-popup/text.tsx +++ b/shared/chat/conversation/messages/message-popup/text.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import {useConfigState} from '@/stores/config' import type * as React from 'react' import * as Kb from '@/common-adapters' @@ -23,7 +24,7 @@ const emptyMessage = Chat.makeMessageText({}) const PopText = (ownProps: OwnProps) => { const {ordinal, attachTo, mode, onHidden, position, style, visible} = ownProps - const message = Chat.useChatContext(s => { + const message = ConvoState.useChatContext(s => { const m = s.messageMap.get(ordinal) const message = m ?? emptyMessage return message @@ -64,7 +65,7 @@ const PopText = (ownProps: OwnProps) => { })() const yourMessage = author === you - const {isTeam, messageReplyPrivately, numPart, teamType} = Chat.useChatContext( + const {isTeam, messageReplyPrivately, numPart, teamType} = ConvoState.useChatContext( C.useShallow(s => { const {teamType, teamname} = s.meta const isTeam = !!teamname diff --git a/shared/chat/conversation/messages/pin/index.tsx b/shared/chat/conversation/messages/pin/index.tsx index 68bf1e8cccd4..ab00ff4bc351 100644 --- a/shared/chat/conversation/messages/pin/index.tsx +++ b/shared/chat/conversation/messages/pin/index.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import type * as T from '@/constants/types' @@ -6,7 +6,7 @@ type Props = {messageID: T.Chat.MessageID} const Pin = (props: Props) => { const {messageID} = props - const replyJump = Chat.useChatContext(s => s.dispatch.replyJump) + const replyJump = ConvoState.useChatContext(s => s.dispatch.replyJump) const onReplyClick = () => replyJump(messageID) return ( diff --git a/shared/chat/conversation/messages/react-button.tsx b/shared/chat/conversation/messages/react-button.tsx index 3fbcd7766bc3..c92b796cc40c 100644 --- a/shared/chat/conversation/messages/react-button.tsx +++ b/shared/chat/conversation/messages/react-button.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type {StylesCrossPlatform} from '@/styles' import {useOrdinal} from './ids-context' import * as Kb from '@/common-adapters' @@ -107,7 +107,7 @@ type NewReactionButtonProps = { export function NewReactionButton(p: NewReactionButtonProps) { const ordinal = useOrdinal() const isDarkMode = useColorScheme() === 'dark' - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onOpenEmojiPicker = () => { navigateAppend(conversationIDKey => ({ name: 'chatChooseEmoji', diff --git a/shared/chat/conversation/messages/reaction-tooltip.tsx b/shared/chat/conversation/messages/reaction-tooltip.tsx index 1b68cfa5fefc..af13035a2763 100644 --- a/shared/chat/conversation/messages/reaction-tooltip.tsx +++ b/shared/chat/conversation/messages/reaction-tooltip.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import type * as React from 'react' import ReactButton from './react-button' @@ -32,14 +33,14 @@ type Section = { const ReactionTooltip = (p: OwnProps) => { const {ordinal, onHidden, attachmentRef, onMouseLeave, onMouseOver, visible, emoji} = p - const reactions = Chat.useChatContext(s => { + const reactions = ConvoState.useChatContext(s => { const message = s.messageMap.get(ordinal) return message && Chat.isMessageWithReactions(message) ? message.reactions : undefined }) const usersInfo = useUsersState(s => (reactions ? s.infoMap : emptyUsersInfo)) - const toggleMessageReaction = Chat.useChatContext(s => s.dispatch.toggleMessageReaction) + const toggleMessageReaction = ConvoState.useChatContext(s => s.dispatch.toggleMessageReaction) - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onAddReaction = () => { onHidden() navigateAppend(conversationIDKey => ({ @@ -70,7 +71,7 @@ const ReactionTooltip = (p: OwnProps) => { reactionsToShow = reactionsToShow.filter(r => r.emoji === emoji) } const insets = Kb.useSafeAreaInsets() - const conversationIDKey = Chat.useChatContext(s => s.id) + const conversationIDKey = ConvoState.useChatContext(s => s.id) const messageContext = {isHighlighted: false, ordinal} if (!visible) { return null @@ -114,7 +115,7 @@ const ReactionTooltip = (p: OwnProps) => { style={styles.overlay} > {/* need context since this uses a portal... */} - + { )} - + ) } diff --git a/shared/chat/conversation/messages/reset-user.tsx b/shared/chat/conversation/messages/reset-user.tsx index 315d02340302..44126bda3d20 100644 --- a/shared/chat/conversation/messages/reset-user.tsx +++ b/shared/chat/conversation/messages/reset-user.tsx @@ -1,12 +1,12 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import {navToProfile} from '@/constants/router' const ResetUser = () => { - const meta = Chat.useChatContext(s => s.meta) - const participantInfo = Chat.useChatContext(s => s.participants) - const resetChatWithoutThem = Chat.useChatContext(s => s.dispatch.resetChatWithoutThem) - const resetLetThemIn = Chat.useChatContext(s => s.dispatch.resetLetThemIn) + const meta = ConvoState.useChatContext(s => s.meta) + const participantInfo = ConvoState.useChatContext(s => s.participants) + const resetChatWithoutThem = ConvoState.useChatContext(s => s.dispatch.resetChatWithoutThem) + const resetLetThemIn = ConvoState.useChatContext(s => s.dispatch.resetLetThemIn) const _participants = participantInfo.all const _resetParticipants = meta.resetParticipants const _viewProfile = navToProfile diff --git a/shared/chat/conversation/messages/retention-notice.tsx b/shared/chat/conversation/messages/retention-notice.tsx index 7767a95ddb03..077496fcc94f 100644 --- a/shared/chat/conversation/messages/retention-notice.tsx +++ b/shared/chat/conversation/messages/retention-notice.tsx @@ -1,5 +1,5 @@ import type * as T from '@/constants/types' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' @@ -40,12 +40,12 @@ function makeRetentionNotice( } function RetentionNoticeContainer() { - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const {teamType, retentionPolicy: policy, teamRetentionPolicy: teamPolicy} = meta const canChange = Teams.useTeamsState(s => { return meta.teamType !== 'adhoc' ? Teams.getCanPerformByID(s, meta.teamID).setRetentionPolicy : true }) - const showInfoPanel = Chat.useChatContext(s => s.dispatch.showInfoPanel) + const showInfoPanel = ConvoState.useChatContext(s => s.dispatch.showInfoPanel) const onChange = () => showInfoPanel(true, 'settings') const explanation = makeRetentionNotice(policy, teamPolicy, teamType) ?? undefined diff --git a/shared/chat/conversation/messages/separator.tsx b/shared/chat/conversation/messages/separator.tsx index ba7704df6e40..cd68b589d016 100644 --- a/shared/chat/conversation/messages/separator.tsx +++ b/shared/chat/conversation/messages/separator.tsx @@ -1,5 +1,6 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' @@ -13,7 +14,7 @@ const useSeparatorData = (trailingItem: T.Chat.Ordinal, leadingItem: T.Chat.Ordi const ordinal = Kb.Styles.isMobile ? leadingItem : trailingItem const orangeOrdinal = React.useContext(OrangeLineContext) - return Chat.useChatContext( + return ConvoState.useChatContext( C.useShallow(s => { const previous = s.separatorMap.get(ordinal) ?? T.Chat.numberToOrdinal(0) const m = s.messageMap.get(ordinal) ?? missingMessage diff --git a/shared/chat/conversation/messages/special-bottom-message.tsx b/shared/chat/conversation/messages/special-bottom-message.tsx index 7af99e8c7491..d422a2d40640 100644 --- a/shared/chat/conversation/messages/special-bottom-message.tsx +++ b/shared/chat/conversation/messages/special-bottom-message.tsx @@ -1,10 +1,11 @@ import * as C from '@/constants' import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import OldProfileReset from './system-old-profile-reset-notice/container' import ResetUser from './reset-user' function BottomMessageContainer() { - const {showSuperseded, showResetParticipants} = Chat.useChatContext( + const {showSuperseded, showResetParticipants} = ConvoState.useChatContext( C.useShallow(s => { const meta = s.meta const showResetParticipants = meta.resetParticipants.size !== 0 diff --git a/shared/chat/conversation/messages/special-top-message.tsx b/shared/chat/conversation/messages/special-top-message.tsx index ee208ca1c23b..265edc9c34ff 100644 --- a/shared/chat/conversation/messages/special-top-message.tsx +++ b/shared/chat/conversation/messages/special-top-message.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import * as React from 'react' @@ -16,10 +16,9 @@ import {useCurrentUserState} from '@/stores/current-user' const ErrorMessage = () => { const createConversationError = useChatThreadRouteParams()?.createConversationError - const createConversation = Chat.useChatState(s => s.dispatch.createConversation) const _onCreateWithoutThem = (allowedUsers: ReadonlyArray) => { - createConversation(allowedUsers) + ConvoState.createConversation(allowedUsers) } const navigateToInbox = C.Router2.navigateToInbox @@ -110,7 +109,7 @@ const ErrorMessage = () => { function SpecialTopMessage() { const username = useCurrentUserState(s => s.username) - const data = Chat.useChatContext( + const data = ConvoState.useChatContext( C.useShallow(s => { const ordinals = s.messageOrdinals const hasLoadedEver = ordinals !== undefined @@ -119,9 +118,9 @@ function SpecialTopMessage() { const {teamType, supersedes, retentionPolicy, teamRetentionPolicy} = meta const loadMoreType = s.moreToLoadBack ? 'moreToLoad' : 'noMoreToLoad' const pendingState = - s.id === Chat.pendingWaitingConversationIDKey + s.id === T.Chat.pendingWaitingConversationIDKey ? 'waiting' - : s.id === Chat.pendingErrorConversationIDKey + : s.id === T.Chat.pendingErrorConversationIDKey ? 'error' : 'done' @@ -131,7 +130,7 @@ function SpecialTopMessage() { const isSelfConversation = teamType === 'adhoc' && partNum === 1 && partAll.includes(username) const showTeamOffer = hasLoadedEver && loadMoreType === 'noMoreToLoad' && teamType === 'adhoc' && partNum > 2 - const hasOlderResetConversation = supersedes !== Chat.noConversationIDKey + const hasOlderResetConversation = supersedes !== T.Chat.noConversationIDKey // don't show default header in the case of the retention notice being visible const showRetentionNotice = retentionPolicy.type !== 'retain' && diff --git a/shared/chat/conversation/messages/system-added-to-team/container.tsx b/shared/chat/conversation/messages/system-added-to-team/container.tsx index 5cf17a3d1ee9..6aa957a3ff77 100644 --- a/shared/chat/conversation/messages/system-added-to-team/container.tsx +++ b/shared/chat/conversation/messages/system-added-to-team/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' @@ -13,7 +13,7 @@ type OwnProps = {message: T.Chat.MessageSystemAddedToTeam} function SystemAddedToTeamContainer(p: OwnProps) { const {message} = p const {addee, adder, author, bulkAdds, role: _role, timestamp} = message - const {teamID, teamname, teamType, showInfoPanel} = Chat.useChatContext( + const {teamID, teamname, teamType, showInfoPanel} = ConvoState.useChatContext( C.useShallow(s => ({ showInfoPanel: s.dispatch.showInfoPanel, teamID: s.meta.teamID, @@ -34,7 +34,7 @@ function SystemAddedToTeamContainer(p: OwnProps) { showInfoPanel(true, 'settings') } - const navigateAppend = Chat.useChatNavigateAppend() + const navigateAppend = ConvoState.useChatNavigateAppend() const onViewBot = () => { navigateAppend(conversationIDKey => ({ name: 'chatInstallBot', diff --git a/shared/chat/conversation/messages/system-change-retention/container.tsx b/shared/chat/conversation/messages/system-change-retention/container.tsx index ec98b2387414..ffae15f5f95f 100644 --- a/shared/chat/conversation/messages/system-change-retention/container.tsx +++ b/shared/chat/conversation/messages/system-change-retention/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' @@ -13,7 +13,7 @@ function SystemChangeRetentionContainer(p: OwnProps) { const {message} = p const {isInherit, isTeam, membersType, policy, user} = message const you = useCurrentUserState(s => s.username) - const {teamType, teamname, showInfoPanel} = Chat.useChatContext( + const {teamType, teamname, showInfoPanel} = ConvoState.useChatContext( C.useShallow(s => ({ showInfoPanel: s.dispatch.showInfoPanel, teamType: s.meta.teamType, diff --git a/shared/chat/conversation/messages/system-create-team/container.tsx b/shared/chat/conversation/messages/system-create-team/container.tsx index 8fd7fb55d1f5..7307790beae1 100644 --- a/shared/chat/conversation/messages/system-create-team/container.tsx +++ b/shared/chat/conversation/messages/system-create-team/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' @@ -10,7 +10,7 @@ type OwnProps = {message: T.Chat.MessageSystemCreateTeam} function SystemCreateTeamContainer(p: OwnProps) { const {creator} = p.message - const {showInfoPanel, teamID, teamname} = Chat.useChatContext( + const {showInfoPanel, teamID, teamname} = ConvoState.useChatContext( C.useShallow(s => { const {teamID, teamname} = s.meta const {showInfoPanel} = s.dispatch diff --git a/shared/chat/conversation/messages/system-invite-accepted/container.tsx b/shared/chat/conversation/messages/system-invite-accepted/container.tsx index 745f67cbbbb9..2e3de390cd50 100644 --- a/shared/chat/conversation/messages/system-invite-accepted/container.tsx +++ b/shared/chat/conversation/messages/system-invite-accepted/container.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Teams from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' @@ -11,7 +11,7 @@ type OwnProps = {message: T.Chat.MessageSystemInviteAccepted} function SystemInviteAcceptedContainer(p: OwnProps) { const {message} = p const {role} = message - const teamID = Chat.useChatContext(s => s.meta.teamID) + const teamID = ConvoState.useChatContext(s => s.meta.teamID) const you = useCurrentUserState(s => s.username) const navigateAppend = C.Router2.navigateAppend const onViewTeam = () => { diff --git a/shared/chat/conversation/messages/system-joined/container.tsx b/shared/chat/conversation/messages/system-joined/container.tsx index 7df38429febe..dea7c8a4a4e6 100644 --- a/shared/chat/conversation/messages/system-joined/container.tsx +++ b/shared/chat/conversation/messages/system-joined/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' @@ -10,7 +10,7 @@ type OwnProps = {message: T.Chat.MessageSystemJoined} function JoinedContainer(p: OwnProps) { const {message} = p const {joiners, author, leavers, timestamp} = message - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const {channelname, teamType, teamname} = meta const joiners2 = !joiners?.length && !leavers?.length ? [author] : joiners const isBigTeam = teamType === 'big' @@ -45,13 +45,7 @@ const MultiUserJoinedNotice = (p: { return ( - + {getAddedUsernames(who)} diff --git a/shared/chat/conversation/messages/system-left/container.tsx b/shared/chat/conversation/messages/system-left/container.tsx index 16aeb257ba84..dc7572d399df 100644 --- a/shared/chat/conversation/messages/system-left/container.tsx +++ b/shared/chat/conversation/messages/system-left/container.tsx @@ -1,9 +1,9 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import UserNotice from '../user-notice' function LeftContainer() { - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const {channelname, teamType, teamname} = meta const isBigTeam = teamType === 'big' diff --git a/shared/chat/conversation/messages/system-new-channel/container.tsx b/shared/chat/conversation/messages/system-new-channel/container.tsx index 16fb0094286e..474b6f0cae31 100644 --- a/shared/chat/conversation/messages/system-new-channel/container.tsx +++ b/shared/chat/conversation/messages/system-new-channel/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import {useTeamsState} from '@/stores/teams' import type * as T from '@/constants/types' @@ -8,7 +8,7 @@ type OwnProps = {message: T.Chat.MessageSystemNewChannel} function SystemNewChannelContainer(p: OwnProps) { const {message} = p - const teamID = Chat.useChatContext(s => s.meta.teamID) + const teamID = ConvoState.useChatContext(s => s.meta.teamID) const manageChatChannels = useTeamsState(s => s.dispatch.manageChatChannels) const onManageChannels = () => { manageChatChannels(teamID) @@ -17,9 +17,7 @@ function SystemNewChannelContainer(p: OwnProps) { const descStyleOverride = { link: {fontSize: Kb.Styles.isMobile ? 15 : 13, fontWeight: '600'}, paragraph: { - color: Kb.Styles.isMobile - ? Kb.Styles.globalColors.black_50 - : Kb.Styles.globalColors.black_50OrWhite_40, + color: Kb.Styles.isMobile ? Kb.Styles.globalColors.black_50 : Kb.Styles.globalColors.black_50OrWhite_40, fontSize: Kb.Styles.isMobile ? 15 : 13, }, } as const diff --git a/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx b/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx index 6d730afe9028..c0efb62178e1 100644 --- a/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx +++ b/shared/chat/conversation/messages/system-old-profile-reset-notice/container.tsx @@ -1,17 +1,17 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import {previewConversation} from '@/constants/router' import {Text} from '@/common-adapters' import UserNotice from '../user-notice' const SystemOldProfileResetNotice = () => { - const participantInfo = Chat.useChatContext(s => s.participants) - const meta = Chat.useChatContext(s => s.meta) + const participantInfo = ConvoState.useChatContext(s => s.participants) + const meta = ConvoState.useChatContext(s => s.meta) const _participants = participantInfo.all const nextConversationIDKey = meta.supersededBy const username = meta.wasFinalizedBy || '' const onOpenConversation = (conversationIDKey: T.Chat.ConversationIDKey) => { - Chat.getConvoState(conversationIDKey).dispatch.navigateToThread('jumpFromReset') + ConvoState.getConvoState(conversationIDKey).dispatch.navigateToThread('jumpFromReset') } const startConversation = (participants: ReadonlyArray) => { previewConversation({participants, reason: 'fromAReset'}) diff --git a/shared/chat/conversation/messages/system-profile-reset-notice.tsx b/shared/chat/conversation/messages/system-profile-reset-notice.tsx index 9385c298cce8..31c849cc533e 100644 --- a/shared/chat/conversation/messages/system-profile-reset-notice.tsx +++ b/shared/chat/conversation/messages/system-profile-reset-notice.tsx @@ -1,14 +1,14 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' import UserNotice from './user-notice' const SystemProfileResetNotice = () => { - const meta = Chat.useChatContext(s => s.meta) + const meta = ConvoState.useChatContext(s => s.meta) const prevConversationIDKey = meta.supersedes const username = meta.wasFinalizedBy || '' const _onOpenOlderConversation = (conversationIDKey: T.Chat.ConversationIDKey) => { - Chat.getConvoState(conversationIDKey).dispatch.navigateToThread('jumpToReset') + ConvoState.getConvoState(conversationIDKey).dispatch.navigateToThread('jumpToReset') } const onOpenOlderConversation = () => { prevConversationIDKey && _onOpenOlderConversation(prevConversationIDKey) diff --git a/shared/chat/conversation/messages/system-simple-to-complex/container.tsx b/shared/chat/conversation/messages/system-simple-to-complex/container.tsx index 86c668a4b7ec..5acdf92ae8bd 100644 --- a/shared/chat/conversation/messages/system-simple-to-complex/container.tsx +++ b/shared/chat/conversation/messages/system-simple-to-complex/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import {useTeamsState} from '@/stores/teams' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' @@ -9,7 +9,7 @@ type OwnProps = {message: T.Chat.MessageSystemSimpleToComplex} function SystemSimpleToComplexContainer(p: OwnProps) { const {message} = p - const teamID = Chat.useChatContext(s => s.meta.teamID) + const teamID = ConvoState.useChatContext(s => s.meta.teamID) const you = useCurrentUserState(s => s.username) const manageChatChannels = useTeamsState(s => s.dispatch.manageChatChannels) const onManageChannels = () => { diff --git a/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx b/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx index aae824d05673..957401ac1a34 100644 --- a/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx +++ b/shared/chat/conversation/messages/system-users-added-to-conv/container.tsx @@ -1,4 +1,4 @@ -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import type * as React from 'react' import type * as T from '@/constants/types' import * as Kb from '@/common-adapters' @@ -9,7 +9,7 @@ type OwnProps = {message: T.Chat.MessageSystemUsersAddedToConversation} function UsersAddedToConversationContainer(p: OwnProps) { const {usernames} = p.message - const channelname = Chat.useChatContext(s => s.meta.channelname) + const channelname = ConvoState.useChatContext(s => s.meta.channelname) const you = useCurrentUserState(s => s.username) let otherUsers: Array | undefined if (usernames.includes(you)) { diff --git a/shared/chat/conversation/messages/text/coinflip/index.tsx b/shared/chat/conversation/messages/text/coinflip/index.tsx index 29036345f59a..7d4b27abef8a 100644 --- a/shared/chat/conversation/messages/text/coinflip/index.tsx +++ b/shared/chat/conversation/messages/text/coinflip/index.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as Kb from '@/common-adapters' import * as T from '@/constants/types' import CoinFlipError from './errors' @@ -10,7 +10,7 @@ import {pluralize} from '@/util/string' function CoinFlipContainer() { const ordinal = useOrdinal() - const {isSendError, text, flipGameID, sendMessage} = Chat.useChatContext( + const {isSendError, text, flipGameID, sendMessage} = ConvoState.useChatContext( C.useShallow(s => { const message = s.messageMap.get(ordinal) const isSendError = message?.type === 'text' ? !!message.errorReason : false @@ -20,7 +20,7 @@ function CoinFlipContainer() { return {flipGameID, isSendError, message, sendMessage, text} }) ) - const status = Chat.useChatContext(s => s.flipStatusMap.get(flipGameID)) + const status = ConvoState.useChatContext(s => s.flipStatusMap.get(flipGameID)) const onFlipAgain = () => { text && sendMessage(text.stringValue()) } diff --git a/shared/chat/conversation/messages/text/reply.tsx b/shared/chat/conversation/messages/text/reply.tsx index eab74216309b..350a5f27b13d 100644 --- a/shared/chat/conversation/messages/text/reply.tsx +++ b/shared/chat/conversation/messages/text/reply.tsx @@ -1,6 +1,6 @@ import * as Kb from '@/common-adapters' import * as React from 'react' -import * as Chat from '@/stores/chat' +import {zoomImage} from '@/constants/chat/helpers' import {useIsHighlighted} from '../ids-context' import type * as T from '@/constants/types' @@ -38,7 +38,7 @@ const ReplyImage = () => { if (!imageURL) return null const imageHeight = replyTo.previewHeight const imageWidth = replyTo.previewWidth - const sizing = imageWidth && imageHeight ? Chat.zoomImage(imageWidth, imageHeight, 80) : undefined + const sizing = imageWidth && imageHeight ? zoomImage(imageWidth, imageHeight, 80) : undefined return ( diff --git a/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx b/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx index 8f52867ca53e..46af9599e79c 100644 --- a/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx +++ b/shared/chat/conversation/messages/text/unfurl/prompt-list/container.tsx @@ -1,11 +1,11 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as ConvoState from '@/stores/convostate' import * as T from '@/constants/types' import * as Kb from '@/common-adapters' import Prompt from './prompt' function UnfurlPromptListContainer({messageID}: {messageID: T.Chat.MessageID}) { - const {unfurlResolvePrompt, promptDomains} = Chat.useChatContext( + const {unfurlResolvePrompt, promptDomains} = ConvoState.useChatContext( C.useShallow(s => { const unfurlResolvePrompt = s.dispatch.unfurlResolvePrompt const promptDomains = s.unfurlPrompt.get(messageID) diff --git a/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx b/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx index fff9f4e7ccae..89a5573a6b2d 100644 --- a/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx +++ b/shared/chat/conversation/messages/text/unfurl/unfurl-list/image/index.tsx @@ -1,6 +1,6 @@ import type * as React from 'react' import * as Kb from '@/common-adapters/index' -import * as Chat from '@/stores/chat' +import {clampImageSize} from '@/constants/chat/helpers' import {maxWidth} from '@/chat/conversation/messages/attachment/shared' import {Video} from './video' import {openURL} from '@/util/misc' @@ -24,7 +24,7 @@ const UnfurlImage = (p: Props) => { linkURL && openURL(linkURL) } const maxSize = Math.min(maxWidth, 320) - (widthPadding || 0) - const {height, width} = Chat.clampImageSize(p.width, p.height, maxSize, 320) + const {height, width} = clampImageSize(p.width, p.height, maxSize, 320) return isVideo ? (