diff --git a/standalone/src/init.ts b/standalone/src/init.ts index eb53ed3fa..299cef824 100644 --- a/standalone/src/init.ts +++ b/standalone/src/init.ts @@ -28,12 +28,12 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import configureStore from 'mattermost-redux/store'; import {Theme} from 'mattermost-redux/types/themes'; import { - VOICE_CHANNEL_USER_SCREEN_ON, - VOICE_CHANNEL_ROOT_POST, - VOICE_CHANNEL_PROFILES_CONNECTED, - VOICE_CHANNEL_USERS_CONNECTED, - VOICE_CHANNEL_USERS_CONNECTED_STATES, - VOICE_CHANNEL_CALL_START, + USER_SCREEN_ON, + PROFILES_CONNECTED, + USERS_CONNECTED, + USERS_CONNECTED_STATES, + CALL_STATE, + CALL_HOST, } from 'plugin/action_types'; import {getCallsConfig} from 'plugin/actions'; import CallsClient from 'plugin/client'; @@ -145,7 +145,7 @@ async function fetchChannelData(store: Store, channelID: string) { } store.dispatch({ - type: VOICE_CHANNEL_USERS_CONNECTED, + type: USERS_CONNECTED, data: { users: resp.call.users, channelID, @@ -153,15 +153,7 @@ async function fetchChannelData(store: Store, channelID: string) { }); store.dispatch({ - type: VOICE_CHANNEL_ROOT_POST, - data: { - channelID, - rootPost: resp.call.thread_id, - }, - }); - - store.dispatch({ - type: VOICE_CHANNEL_USER_SCREEN_ON, + type: USER_SCREEN_ON, data: { channelID, userID: resp.call.screen_sharing_id, @@ -169,20 +161,28 @@ async function fetchChannelData(store: Store, channelID: string) { }); store.dispatch({ - type: VOICE_CHANNEL_CALL_START, + type: CALL_STATE, data: { ID: resp.call.id, channelID: resp.channel_id, startAt: resp.call.start_at, ownerID: resp.call.owner_id, + threadID: resp.call.thread_id, + }, + }); + + store.dispatch({ + type: CALL_HOST, + data: { + channelID, hostID: resp.call.host_id, - dismissedNotification: resp.call.dismissed_notification || {}, + hostChangeAt: resp.call.start_at, }, }); if (resp.call.users.length > 0) { store.dispatch({ - type: VOICE_CHANNEL_PROFILES_CONNECTED, + type: PROFILES_CONNECTED, data: { profiles: await getProfilesByIds(store.getState(), resp.call.users), channelID, @@ -196,7 +196,7 @@ async function fetchChannelData(store: Store, channelID: string) { userStates[users[i]] = {...states[i], id: users[i], voice: false}; } store.dispatch({ - type: VOICE_CHANNEL_USERS_CONNECTED_STATES, + type: USERS_CONNECTED_STATES, data: { states: userStates, channelID, diff --git a/standalone/src/recording/components/recording_view/index.tsx b/standalone/src/recording/components/recording_view/index.tsx index 1492ed016..2ec05f6eb 100644 --- a/standalone/src/recording/components/recording_view/index.tsx +++ b/standalone/src/recording/components/recording_view/index.tsx @@ -14,7 +14,7 @@ import {useIntl} from 'react-intl'; import {useSelector} from 'react-redux'; import ScreenIcon from 'src/components/icons/screen_icon'; import Timestamp from 'src/components/timestamp'; -import {voiceChannelCallHostID, voiceChannelScreenSharingID, voiceConnectedProfiles, voiceUsersStatuses} from 'src/selectors'; +import {screenSharingIDForCurrentCall, profilesInCurrentCall, usersStatusesInCurrentCall, hostIDForCurrentCall} from 'src/selectors'; import {callProfileImages} from 'src/recording/selectors'; @@ -26,9 +26,9 @@ const RecordingView = () => { const [screenStream, setScreenStream] = useState(null); const callsClient = window.callsClient; const channelID = callsClient?.channelID || ''; - const screenSharingID = useSelector((state: GlobalState) => voiceChannelScreenSharingID(state, channelID)) || ''; - const statuses = useSelector(voiceUsersStatuses); - const profiles = sortedProfiles(useSelector(voiceConnectedProfiles), statuses, screenSharingID); + const screenSharingID = useSelector((state: GlobalState) => screenSharingIDForCurrentCall(state)); + const statuses = useSelector(usersStatusesInCurrentCall); + const profiles = sortedProfiles(useSelector(profilesInCurrentCall), statuses, screenSharingID); const profileImages = useSelector((state: GlobalState) => callProfileImages(state, channelID)); const pictures: {[key: string]: string} = {}; if (profileImages) { @@ -36,7 +36,7 @@ const RecordingView = () => { pictures[String(profiles[i].id)] = profileImages[profiles[i].id]; } } - const callHostID = useSelector((state: GlobalState) => voiceChannelCallHostID(state, channelID)) || ''; + const hostID = useSelector((state: GlobalState) => hostIDForCurrentCall(state)); const attachVoiceTracks = (tracks: MediaStreamTrack[]) => { for (const track of tracks) { @@ -155,7 +155,7 @@ const RecordingView = () => { isSpeaking={isSpeaking} isHandRaised={isHandRaised} reaction={status?.reaction} - isHost={profile.id === callHostID} + isHost={profile.id === hostID} /> ); }); diff --git a/standalone/src/recording/index.tsx b/standalone/src/recording/index.tsx index 25cc2c3af..85b042ea3 100644 --- a/standalone/src/recording/index.tsx +++ b/standalone/src/recording/index.tsx @@ -8,14 +8,14 @@ import {Theme} from 'mattermost-redux/types/themes'; import {getCurrentUserId} from 'mattermost-webapp/packages/mattermost-redux/src/selectors/entities/users'; import {logErr} from 'plugin/log'; import {pluginId} from 'plugin/manifest'; -import {voiceConnectedProfilesInChannel, voiceChannelCallStartAt} from 'plugin/selectors'; +import {profilesInCallInChannel, callStartAtForCallInChannel} from 'plugin/selectors'; import {Store} from 'plugin/types/mattermost-webapp'; import {getProfilesByIds, getPluginPath, fetchTranslationsFile, setCallsGlobalCSSVars, runWithRetry} from 'plugin/utils'; import React from 'react'; import ReactDOM from 'react-dom'; import {IntlProvider} from 'react-intl'; import {Provider} from 'react-redux'; -import {VOICE_CHANNEL_USER_CONNECTED} from 'src/action_types'; +import {USER_CONNECTED} from 'src/action_types'; import init from '../init'; import recordingReducer from 'src/recording/reducers'; @@ -70,12 +70,12 @@ async function initRecordingStore(store: Store, channelID: string) { } async function initRecording(store: Store, theme: Theme, channelID: string) { - if (!voiceChannelCallStartAt(store.getState(), channelID)) { + if (!callStartAtForCallInChannel(store.getState(), channelID)) { throw new Error('call is missing from store'); } await store.dispatch({ - type: VOICE_CHANNEL_USER_CONNECTED, + type: USER_CONNECTED, data: { channelID, userID: getCurrentUserId(store.getState()), @@ -83,7 +83,7 @@ async function initRecording(store: Store, theme: Theme, channelID: string) { }, }); - const profiles = voiceConnectedProfilesInChannel(store.getState(), channelID); + const profiles = profilesInCallInChannel(store.getState(), channelID); if (profiles?.length > 0) { store.dispatch({ diff --git a/standalone/src/widget/index.tsx b/standalone/src/widget/index.tsx index 8922c9b30..4cfab5c13 100644 --- a/standalone/src/widget/index.tsx +++ b/standalone/src/widget/index.tsx @@ -8,7 +8,7 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {Theme} from 'mattermost-redux/types/themes'; import {isOpenChannel, isPrivateChannel} from 'mattermost-redux/utils/channel_utils'; import { - VOICE_CHANNEL_USER_CONNECTED, + USER_CONNECTED, } from 'plugin/action_types'; import CallWidget from 'plugin/components/call_widget'; import { @@ -30,7 +30,7 @@ import init from '../init'; async function initWidget(store: Store, theme: Theme, channelID: string) { store.dispatch({ - type: VOICE_CHANNEL_USER_CONNECTED, + type: USER_CONNECTED, data: { channelID, userID: getCurrentUserId(store.getState()), diff --git a/webapp/src/action_types.ts b/webapp/src/action_types.ts index 91244ef84..99d3dda46 100644 --- a/webapp/src/action_types.ts +++ b/webapp/src/action_types.ts @@ -1,28 +1,27 @@ import {pluginId} from './manifest'; -export const VOICE_CHANNEL_USER_CONNECTED = pluginId + '_voice_channel_user_connected'; -export const VOICE_CHANNEL_USER_DISCONNECTED = pluginId + '_voice_channel_user_disconnected'; -export const VOICE_CHANNEL_USER_MUTED = pluginId + '_voice_channel_user_muted'; -export const VOICE_CHANNEL_USER_UNMUTED = pluginId + '_voice_channel_user_unmuted'; -export const VOICE_CHANNEL_USER_VOICE_ON = pluginId + '_voice_channel_user_voice_on'; -export const VOICE_CHANNEL_USER_VOICE_OFF = pluginId + '_voice_channel_user_voice_off'; -export const VOICE_CHANNEL_USERS_CONNECTED = pluginId + '_voice_channel_users_connected'; -export const VOICE_CHANNEL_USERS_CONNECTED_STATES = pluginId + '_voice_channel_users_connected_states'; -export const VOICE_CHANNEL_PROFILES_CONNECTED = pluginId + '_voice_channel_profiles_connected'; -export const VOICE_CHANNEL_PROFILE_CONNECTED = pluginId + '_voice_channel_profile_connected'; -export const VOICE_CHANNEL_CALL_START = pluginId + '_voice_channel_call_start'; -export const VOICE_CHANNEL_USER_SCREEN_ON = pluginId + '_voice_channel_screen_on'; -export const VOICE_CHANNEL_USER_SCREEN_OFF = pluginId + '_voice_channel_screen_off'; -export const VOICE_CHANNEL_UNINIT = pluginId + '_voice_channel_uninit'; -export const VOICE_CHANNEL_USER_RAISE_HAND = pluginId + '_voice_channel_user_raise_hand'; -export const VOICE_CHANNEL_USER_UNRAISE_HAND = pluginId + '_voice_channel_user_unraise_hand'; -export const VOICE_CHANNEL_USER_REACTED = pluginId + '_voice_channel_user_reacted'; -export const VOICE_CHANNEL_USER_REACTED_TIMEOUT = pluginId + '_voice_channel_user_reacted_timeout'; -export const VOICE_CHANNEL_ROOT_POST = pluginId + '_voice_channel_root_post'; -export const VOICE_CHANNEL_CALL_HOST = pluginId + '_voice_channel_call_host'; -export const VOICE_CHANNEL_CALL_RECORDING_STATE = pluginId + '_voice_channel_call_recording_state'; -export const VOICE_CHANNEL_CALL_REC_PROMPT_DISMISSED = pluginId + '_voice_channel_call_rec_prompt_dismissed'; -export const VOICE_CHANNEL_USER_JOINED_TIMEOUT = pluginId + '_voice_channel_user_joined_timeout'; +export const USER_CONNECTED = pluginId + '_user_connected'; +export const USER_DISCONNECTED = pluginId + '_user_disconnected'; +export const USER_MUTED = pluginId + '_user_muted'; +export const USER_UNMUTED = pluginId + '_user_unmuted'; +export const USER_VOICE_ON = pluginId + '_user_voice_on'; +export const USER_VOICE_OFF = pluginId + '_user_voice_off'; +export const USERS_CONNECTED = pluginId + '_users_connected'; +export const USERS_CONNECTED_STATES = pluginId + '_users_connected_states'; +export const PROFILE_CONNECTED = pluginId + '_profile_connected'; +export const PROFILES_CONNECTED = pluginId + '_profiles_connected'; +export const CALL_STATE = pluginId + '_call_state'; +export const USER_SCREEN_ON = pluginId + '_screen_on'; +export const USER_SCREEN_OFF = pluginId + '_screen_off'; +export const UNINIT = pluginId + '_uninit'; +export const USER_RAISE_HAND = pluginId + '_user_raise_hand'; +export const USER_UNRAISE_HAND = pluginId + '_user_unraise_hand'; +export const USER_REACTED = pluginId + '_user_reacted'; +export const USER_REACTED_TIMEOUT = pluginId + '_user_reacted_timeout'; +export const CALL_HOST = pluginId + '_call_host'; +export const CALL_RECORDING_STATE = pluginId + '_call_recording_state'; +export const CALL_REC_PROMPT_DISMISSED = pluginId + '_call_rec_prompt_dismissed'; +export const USER_JOINED_TIMEOUT = pluginId + '_user_joined_timeout'; export const SHOW_EXPANDED_VIEW = pluginId + '_show_expanded_view'; export const HIDE_EXPANDED_VIEW = pluginId + '_hide_expanded_view'; @@ -39,8 +38,8 @@ export const RINGING_FOR_CALL = pluginId + '_ringing_for_call'; export const DID_RING_FOR_CALL = pluginId + '_did_ring_for_call'; export const DID_NOTIFY_FOR_CALL = pluginId + '_did_notify_for_call'; -// VOICE_CHANNEL_CALL_END is sent when the `/call end` command is used -export const VOICE_CHANNEL_CALL_END = pluginId + '_voice_channel_call_end'; +// CALL_END is sent when the `/call end` command is used +export const CALL_END = pluginId + '_call_end'; export const RECEIVED_CALLS_CONFIG = pluginId + '_received_calls_config'; export const RECORDINGS_ENABLED = pluginId + '_recordings_enabled'; diff --git a/webapp/src/actions.ts b/webapp/src/actions.ts index e53af84ad..5264d44f7 100644 --- a/webapp/src/actions.ts +++ b/webapp/src/actions.ts @@ -21,11 +21,11 @@ import {CallsInTestModeModal, IDTestModeUser} from 'src/components/modals'; import {RING_LENGTH} from 'src/constants'; import {logErr} from 'src/log'; import { - channelHasCall, connectedCallID, incomingCalls, + channelHasCall, idForCurrentCall, incomingCalls, ringingEnabled, ringingForCall, - voiceChannelCallDismissedNotification, - voiceChannelCalls, + callDismissedNotification, + calls, } from 'src/selectors'; import * as Telemetry from 'src/types/telemetry'; import {ChannelType} from 'src/types/types'; @@ -43,9 +43,9 @@ import { SHOW_EXPANDED_VIEW, SHOW_SCREEN_SOURCE_MODAL, SHOW_SWITCH_CALL_MODAL, - VOICE_CHANNEL_CALL_REC_PROMPT_DISMISSED, - VOICE_CHANNEL_CALL_RECORDING_STATE, - VOICE_CHANNEL_USER_DISCONNECTED, + CALL_REC_PROMPT_DISMISSED, + CALL_RECORDING_STATE, + USER_DISCONNECTED, RTCD_ENABLED, REMOVE_INCOMING_CALL, DID_RING_FOR_CALL, @@ -242,7 +242,7 @@ export const startCallRecording = (callID: string) => (dispatch: Dispatch { dispatch({ - type: VOICE_CHANNEL_CALL_RECORDING_STATE, + type: CALL_RECORDING_STATE, data: { callID, recState: { @@ -266,7 +266,7 @@ export const stopCallRecording = async (callID: string) => { export const recordingPromptDismissedAt = (callID: string, dismissedAt: number) => (dispatch: Dispatch) => { dispatch({ - type: VOICE_CHANNEL_CALL_REC_PROMPT_DISMISSED, + type: CALL_REC_PROMPT_DISMISSED, data: { callID, dismissedAt, @@ -316,7 +316,7 @@ export function incomingCallOnChannel(channelID: string, callID: string, callerI return; } - if (voiceChannelCallDismissedNotification(getState(), channelID)) { + if (callDismissedNotification(getState(), channelID)) { return; } @@ -326,8 +326,8 @@ export function incomingCallOnChannel(channelID: string, callID: string, callerI // Never send a notification for a call you started yourself, or a call you are currently in. const currentUserID = getCurrentUserId(getState()); - const connectedID = connectedCallID(getState()); - if (currentUserID === callerID || connectedID === callID) { + const currentCallID = idForCurrentCall(getState()); + if (currentUserID === callerID || currentCallID === callID) { return; } @@ -352,10 +352,10 @@ export function incomingCallOnChannel(channelID: string, callID: string, callerI export const userDisconnected = (channelID: string, userID: string) => { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { // save for later - const callID = voiceChannelCalls(getState())[channelID].ID || ''; + const callID = calls(getState())[channelID].ID || ''; await dispatch({ - type: VOICE_CHANNEL_USER_DISCONNECTED, + type: USER_DISCONNECTED, data: { channelID, userID, diff --git a/webapp/src/components/call_widget/index.ts b/webapp/src/components/call_widget/index.ts index b5fd0235e..b326c6dbb 100644 --- a/webapp/src/components/call_widget/index.ts +++ b/webapp/src/components/call_widget/index.ts @@ -12,18 +12,18 @@ import {bindActionCreators, Dispatch} from 'redux'; import {recordingPromptDismissedAt, showExpandedView, showScreenSourceModal, trackEvent} from 'src/actions'; import { - voiceUsersStatuses, - voiceChannelCallStartAt, - voiceChannelScreenSharingID, + usersStatusesInCurrentCall, + callStartAtForCurrentCall, + screenSharingIDForCurrentCall, expandedView, getChannelUrlAndDisplayName, allowScreenSharing, - voiceConnectedProfiles, - voiceChannelCallHostID, - callRecording, - voiceChannelCallHostChangeAt, - recentlyJoinedUsers, + profilesInCurrentCall, + hostIDForCurrentCall, + hostChangeAtForCurrentCall, + recordingForCurrentCall, sortedIncomingCalls, + recentlyJoinedUsersInCurrentCall, } from 'src/selectors'; import {alphaSortProfiles, stateSortProfiles} from 'src/utils'; @@ -37,14 +37,14 @@ const mapStateToProps = (state: GlobalState) => { const channel = getChannel(state, String(window.callsClient?.channelID)); const currentUserID = getCurrentUserId(state); - const screenSharingID = voiceChannelScreenSharingID(state, channel?.id) || ''; + const screenSharingID = screenSharingIDForCurrentCall(state); const sortedProfiles = (profiles: UserProfile[], statuses: {[key: string]: UserState}) => { return [...profiles].sort(alphaSortProfiles).sort(stateSortProfiles(profiles, statuses, screenSharingID, true)); }; - const statuses = voiceUsersStatuses(state); - const profiles = sortedProfiles(voiceConnectedProfiles(state), statuses); + const statuses = usersStatusesInCurrentCall(state); + const profiles = sortedProfiles(profilesInCurrentCall(state), statuses); const profilesMap: IDMappedObjects = {}; const picturesMap: { @@ -67,15 +67,15 @@ const mapStateToProps = (state: GlobalState) => { profiles, profilesMap, picturesMap, - statuses: voiceUsersStatuses(state) || {}, - callStartAt: voiceChannelCallStartAt(state, channel?.id) || Number(window.callsClient?.initTime), - callHostID: voiceChannelCallHostID(state, channel?.id) || '', - callHostChangeAt: voiceChannelCallHostChangeAt(state, channel?.id) || 0, - callRecording: callRecording(state, channel?.id), + statuses, + callStartAt: callStartAtForCurrentCall(state), + callHostID: hostIDForCurrentCall(state), + callHostChangeAt: hostChangeAtForCurrentCall(state), + callRecording: recordingForCurrentCall(state), screenSharingID, allowScreenSharing: allowScreenSharing(state), show: !expandedView(state), - recentlyJoinedUsers: recentlyJoinedUsers(state, channel?.id), + recentlyJoinedUsers: recentlyJoinedUsersInCurrentCall(state), wider: getMyTeams(state)?.length > 1, callsIncoming: sortedIncomingCalls(state), }; diff --git a/webapp/src/components/channel_call_toast.tsx b/webapp/src/components/channel_call_toast.tsx index af2304fae..068fc0303 100644 --- a/webapp/src/components/channel_call_toast.tsx +++ b/webapp/src/components/channel_call_toast.tsx @@ -9,27 +9,27 @@ import ActiveCallIcon from 'src/components/icons/active_call_icon'; import {useDismissJoin} from 'src/components/incoming_calls/hooks'; import Timestamp from 'src/components/timestamp'; import { - connectedChannelID, + channelIDForCurrentCall, dismissedCallForCurrentChannel, isLimitRestricted, - voiceChannelCallInCurrentChannel, - voiceConnectedCurrentChannel, - voiceProfilesInCurrentChannel, + callInCurrentChannel, + usersInCallInCurrentChannel, + profilesInCallInCurrentChannel, } from 'src/selectors'; import {callStartedTimestampFn} from 'src/utils'; const ChannelCallToast = () => { const intl = useIntl(); const currChannelID = useSelector(getCurrentChannelId); - const connectedID = useSelector(connectedChannelID); - const connectedUsers = useSelector(voiceConnectedCurrentChannel); - const call = useSelector(voiceChannelCallInCurrentChannel); - const profiles = useSelector(voiceProfilesInCurrentChannel); + const connectedID = useSelector(channelIDForCurrentCall); + const connectedUsers = useSelector(usersInCallInCurrentChannel); + const call = useSelector(callInCurrentChannel); + const profiles = useSelector(profilesInCallInCurrentChannel); const limitRestricted = useSelector(isLimitRestricted); const dismissed = useSelector(dismissedCallForCurrentChannel); const [pictures, setPictures] = useState([]); - const callID = useSelector(voiceChannelCallInCurrentChannel)?.ID || ''; + const callID = useSelector(callInCurrentChannel)?.ID || ''; const [onDismiss, onJoin] = useDismissJoin(currChannelID, callID); useMemo(() => { diff --git a/webapp/src/components/channel_header_button/index.ts b/webapp/src/components/channel_header_button/index.ts index f63bcd615..00e94d570 100644 --- a/webapp/src/components/channel_header_button/index.ts +++ b/webapp/src/components/channel_header_button/index.ts @@ -4,8 +4,8 @@ import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/user import {connect} from 'react-redux'; import { - voiceConnectedUsers, - connectedChannelID, + usersInCallInCurrentChannel, + channelIDForCurrentCall, isCloudProfessionalOrEnterpriseOrTrial, isLimitRestricted, maxParticipants, @@ -19,8 +19,8 @@ const mapStateToProps = (state: GlobalState) => { const channel = getCurrentChannel(state); return { show: callsShowButton(state, channel?.id), - inCall: Boolean(connectedChannelID(state) && connectedChannelID(state) === channel?.id), - hasCall: voiceConnectedUsers(state).length > 0, + inCall: Boolean(channelIDForCurrentCall(state) && channelIDForCurrentCall(state) === channel?.id), + hasCall: usersInCallInCurrentChannel(state).length > 0, isAdmin: isCurrentUserSystemAdmin(state), isCloudStarter: isCloudStarter(state), isCloudPaid: isCloudProfessionalOrEnterpriseOrTrial(state), diff --git a/webapp/src/components/channel_header_dropdown_button/index.ts b/webapp/src/components/channel_header_dropdown_button/index.ts index 255eaa7ac..27643b8e6 100644 --- a/webapp/src/components/channel_header_dropdown_button/index.ts +++ b/webapp/src/components/channel_header_dropdown_button/index.ts @@ -4,8 +4,8 @@ import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/user import {connect} from 'react-redux'; import { - voiceConnectedUsers, - connectedChannelID, + usersInCallInCurrentChannel, + channelIDForCurrentCall, isCloudProfessionalOrEnterpriseOrTrial, isLimitRestricted, maxParticipants, @@ -20,8 +20,8 @@ const mapStateToProps = (state: GlobalState) => { return { show: callsShowButton(state, channel?.id), - inCall: Boolean(connectedChannelID(state) && connectedChannelID(state) === channel?.id), - hasCall: voiceConnectedUsers(state).length > 0, + inCall: Boolean(channelIDForCurrentCall(state) && channelIDForCurrentCall(state) === channel?.id), + hasCall: usersInCallInCurrentChannel(state).length > 0, isAdmin: isCurrentUserSystemAdmin(state), isCloudStarter: isCloudStarter(state), isCloudPaid: isCloudProfessionalOrEnterpriseOrTrial(state), diff --git a/webapp/src/components/channel_link_label/index.ts b/webapp/src/components/channel_link_label/index.ts index 27b0b8441..6f6f02fe8 100644 --- a/webapp/src/components/channel_link_label/index.ts +++ b/webapp/src/components/channel_link_label/index.ts @@ -3,7 +3,7 @@ import {GlobalState} from '@mattermost/types/store'; import {UserProfile} from '@mattermost/types/users'; import {connect} from 'react-redux'; -import {voiceConnectedChannels, voiceConnectedProfilesInChannel} from 'src/selectors'; +import {usersInCallInChannel, profilesInCallInChannel} from 'src/selectors'; import ChannelLinkLabel from './component'; @@ -13,14 +13,11 @@ interface OwnProps { const mapStateToProps = (state: GlobalState, ownProps: OwnProps) => { let hasCall = false; - const channels = voiceConnectedChannels(state); + const users = usersInCallInChannel(state, ownProps.channel.id); let profiles: UserProfile[] = []; - if (channels) { - const users = channels[ownProps.channel.id]; - if (users && users.length > 0) { - hasCall = true; - profiles = voiceConnectedProfilesInChannel(state, ownProps.channel.id); - } + if (users && users.length > 0) { + hasCall = true; + profiles = profilesInCallInChannel(state, ownProps.channel.id); } return { hasCall, diff --git a/webapp/src/components/custom_post_types/post_type/component.tsx b/webapp/src/components/custom_post_types/post_type/component.tsx index e7e1f23da..d5fb904da 100644 --- a/webapp/src/components/custom_post_types/post_type/component.tsx +++ b/webapp/src/components/custom_post_types/post_type/component.tsx @@ -17,7 +17,7 @@ import LeaveCallIcon from 'src/components/icons/leave_call_icon'; import {useDismissJoin} from 'src/components/incoming_calls/hooks'; import {Header, SubHeader} from 'src/components/shared'; import Timestamp from 'src/components/timestamp'; -import {voiceChannelCallID} from 'src/selectors'; +import {idForCallInChannel} from 'src/selectors'; import { shouldRenderDesktopWidget, sendDesktopEvent, @@ -51,7 +51,7 @@ const PostType = ({ const timeFormat = {...DateTime.TIME_24_SIMPLE, hourCycle}; const user = useSelector((state: GlobalState) => getUser(state, post.user_id)); - const callID = useSelector((state: GlobalState) => voiceChannelCallID(state, post.channel_id)) || ''; + const callID = useSelector((state: GlobalState) => idForCallInChannel(state, post.channel_id)) || ''; const [, onJoin] = useDismissJoin(post.channel_id, callID); const timestampFn = useCallback(() => { diff --git a/webapp/src/components/custom_post_types/post_type/index.ts b/webapp/src/components/custom_post_types/post_type/index.ts index 35d799c6b..89e06ec61 100644 --- a/webapp/src/components/custom_post_types/post_type/index.ts +++ b/webapp/src/components/custom_post_types/post_type/index.ts @@ -9,9 +9,9 @@ import {connect} from 'react-redux'; import PostType from 'src/components/custom_post_types/post_type/component'; import { - voiceConnectedChannels, - voiceConnectedProfilesInChannel, - connectedChannelID, + usersInCallInChannel, + profilesInCallInChannel, + channelIDForCurrentCall, isCloudProfessionalOrEnterpriseOrTrial, maxParticipants, } from 'src/selectors'; @@ -21,22 +21,19 @@ interface OwnProps { } const mapStateToProps = (state: GlobalState, ownProps: OwnProps) => { - const channels = voiceConnectedChannels(state); + const users = usersInCallInChannel(state, ownProps.post.channel_id); let profiles: UserProfile[] = []; const pictures = []; - if (channels) { - const users = channels[ownProps.post.channel_id]; - if (users && users.length > 0) { - profiles = voiceConnectedProfilesInChannel(state, ownProps.post.channel_id); - for (let i = 0; i < profiles.length; i++) { - pictures.push(Client4.getProfilePictureUrl(profiles[i].id, profiles[i].last_picture_update)); - } + if (users && users.length > 0) { + profiles = profilesInCallInChannel(state, ownProps.post.channel_id); + for (let i = 0; i < profiles.length; i++) { + pictures.push(Client4.getProfilePictureUrl(profiles[i].id, profiles[i].last_picture_update)); } } return { ...ownProps, - connectedID: connectedChannelID(state) || '', + connectedID: channelIDForCurrentCall(state) || '', pictures, profiles, isCloudPaid: isCloudProfessionalOrEnterpriseOrTrial(state), diff --git a/webapp/src/components/end_call_modal/index.ts b/webapp/src/components/end_call_modal/index.ts index c0d34b36d..6a31b5230 100644 --- a/webapp/src/components/end_call_modal/index.ts +++ b/webapp/src/components/end_call_modal/index.ts @@ -5,14 +5,14 @@ import {connect} from 'react-redux'; import {bindActionCreators, Dispatch} from 'redux'; import {hideEndCallModal} from '../../actions'; -import {voiceConnectedUsersInChannel, endCallModal} from '../../selectors'; +import {usersInCallInChannel, endCallModal} from '../../selectors'; import {isDMChannel, getUserIdFromDM} from '../../utils'; import EndCallModal from './component'; const mapStateToProps = (state: GlobalState) => { const endCallState = endCallModal(state); - const connectedUsers = voiceConnectedUsersInChannel(state, endCallState.targetID); + const connectedUsers = usersInCallInChannel(state, endCallState.targetID); const channel = getChannel(state, endCallState.targetID); diff --git a/webapp/src/components/expanded_view/component.tsx b/webapp/src/components/expanded_view/component.tsx index 2d87369b1..eb83d6005 100644 --- a/webapp/src/components/expanded_view/component.tsx +++ b/webapp/src/components/expanded_view/component.tsx @@ -44,6 +44,7 @@ import {ReactionStream} from 'src/components/reaction_stream/reaction_stream'; import { CallAlertConfigs, } from 'src/constants'; +import {logErr} from 'src/log'; import { MUTE_UNMUTE, RAISE_LOWER_HAND, @@ -107,8 +108,8 @@ interface Props extends RouteComponentProps { closeRhs?: () => void, isRhsOpen?: boolean, screenSharingID: string, - channel: Channel, - channelTeam: Team, + channel?: Channel, + channelTeam?: Team, channelDisplayName: string; connectedDMUser: UserProfile | undefined, threadID: Post['id'], @@ -443,6 +444,11 @@ export default class ExpandedView extends React.PureComponent { } onRecordToggle = async (fromShortcut?: boolean) => { + if (!this.props.channel) { + logErr('channel should be defined'); + return; + } + if (this.props.isRecording) { await stopCallRecording(this.props.channel.id); this.props.trackEvent(Telemetry.Event.StopRecording, Telemetry.Source.ExpandedView, {initiator: fromShortcut ? 'shortcut' : 'button'}); @@ -651,7 +657,7 @@ export default class ExpandedView extends React.PureComponent { if (this.props.isRhsOpen && this.props.rhsSelectedThreadID === this.props.threadID) { // close rhs this.props.closeRhs?.(); - } else if (this.props.channel.team_id && this.props.channel.team_id !== this.props.currentTeamID) { + } else if (this.props.channelTeam && this.props.channelTeam.id !== this.props.currentTeamID) { // go to call thread in channels this.props.history.push(`/${this.props.channelTeam.name}/pl/${this.props.threadID}`); } else if (this.props.threadID) { @@ -668,6 +674,11 @@ export default class ExpandedView extends React.PureComponent { } dismissRecordingPrompt = () => { + if (!this.props.channel) { + logErr('channel should be defined'); + return; + } + // Dismiss our prompt. this.props.recordingPromptDismissedAt(this.props.channel.id, Date.now()); @@ -973,13 +984,13 @@ export default class ExpandedView extends React.PureComponent { let chatToolTipText = showChatThread ? formatMessage({defaultMessage: 'Hide chat'}) : formatMessage({defaultMessage: 'Show chat'}); const chatToolTipSubtext = ''; - const chatDisabled = Boolean(this.props.channel?.team_id) && this.props.channel.team_id !== this.props.currentTeamID; + const chatDisabled = this.props.channelTeam && this.props.channelTeam.id !== this.props.currentTeamID; if (chatDisabled) { chatToolTipText = formatMessage({ defaultMessage: 'Chat unavailable: different team selected. Click here to switch back to {channelName} in {teamName}.', }, { channelName: this.props.channelDisplayName, - teamName: this.props.channelTeam.display_name, + teamName: this.props.channelTeam!.display_name, }); } diff --git a/webapp/src/components/expanded_view/index.ts b/webapp/src/components/expanded_view/index.ts index e09e56c40..82ce5d98a 100644 --- a/webapp/src/components/expanded_view/index.ts +++ b/webapp/src/components/expanded_view/index.ts @@ -2,7 +2,6 @@ import {UserState} from '@calls/common/lib/types'; import {GlobalState} from '@mattermost/types/store'; import {UserProfile} from '@mattermost/types/users'; import {Client4} from 'mattermost-redux/client'; -import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentTeamId, getTeam} from 'mattermost-redux/selectors/entities/teams'; import {getThread} from 'mattermost-redux/selectors/entities/threads'; import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users'; @@ -19,20 +18,20 @@ import { } from 'src/actions'; import { allowScreenSharing, - callRecording, - isRecording, - connectedChannelID, + recordingForCurrentCall, + isRecordingInCurrentCall, expandedView, getChannelUrlAndDisplayName, recordingMaxDuration, recordingsEnabled, - voiceChannelCallHostChangeAt, - voiceChannelCallHostID, - voiceChannelCallStartAt, - voiceChannelRootPost, - voiceChannelScreenSharingID, - voiceConnectedProfiles, - voiceUsersStatuses, + hostIDForCurrentCall, + hostChangeAtForCurrentCall, + callStartAtForCurrentCall, + callThreadIDForCallInChannel, + screenSharingIDForCurrentCall, + usersStatusesInCurrentCall, + profilesInCurrentCall, + channelForCurrentCall, } from 'src/selectors'; import {alphaSortProfiles, getUserIdFromDM, isDMChannel, stateSortProfiles} from 'src/utils'; import {closeRhs, getIsRhsOpen, getRhsSelectedPostId, selectRhsPost} from 'src/webapp_globals'; @@ -42,17 +41,17 @@ import ExpandedView from './component'; const mapStateToProps = (state: GlobalState) => { const currentUserID = getCurrentUserId(state); const currentTeamID = getCurrentTeamId(state); - const channel = getChannel(state, connectedChannelID(state) || ''); - const channelTeam = getTeam(state, channel?.team_id); - const screenSharingID = voiceChannelScreenSharingID(state, channel?.id) || ''; - const threadID = voiceChannelRootPost(state, channel?.id); + const channel = channelForCurrentCall(state); + const channelTeam = getTeam(state, channel?.team_id || ''); + const screenSharingID = screenSharingIDForCurrentCall(state); + const threadID = callThreadIDForCallInChannel(state, channel?.id || ''); const sortedProfiles = (profiles: UserProfile[], statuses: { [key: string]: UserState }) => { return [...profiles].sort(alphaSortProfiles).sort(stateSortProfiles(profiles, statuses, screenSharingID, true)); }; - const statuses = voiceUsersStatuses(state); - const profiles = sortedProfiles(voiceConnectedProfiles(state), statuses); + const statuses = usersStatusesInCurrentCall(state); + const profiles = sortedProfiles(profilesInCurrentCall(state), statuses); const pictures: { [key: string]: string } = {}; for (let i = 0; i < profiles.length; i++) { @@ -76,11 +75,11 @@ const mapStateToProps = (state: GlobalState) => { profiles, pictures, statuses, - callStartAt: voiceChannelCallStartAt(state, channel?.id) || 0, - callHostID: voiceChannelCallHostID(state, channel?.id) || '', - callHostChangeAt: voiceChannelCallHostChangeAt(state, channel?.id) || 0, - callRecording: callRecording(state, channel?.id), - isRecording: isRecording(state, channel?.id), + callStartAt: callStartAtForCurrentCall(state), + callHostID: hostIDForCurrentCall(state), + callHostChangeAt: hostChangeAtForCurrentCall(state), + callRecording: recordingForCurrentCall(state), + isRecording: isRecordingInCurrentCall(state), screenSharingID, channel, channelTeam, diff --git a/webapp/src/components/incoming_calls/hooks.ts b/webapp/src/components/incoming_calls/hooks.ts index 6b0db7712..abb383398 100644 --- a/webapp/src/components/incoming_calls/hooks.ts +++ b/webapp/src/components/incoming_calls/hooks.ts @@ -18,8 +18,8 @@ import {dismissIncomingCallNotification, ringForCall, showSwitchCallModal, track import {DEFAULT_RING_SOUND} from 'src/constants'; import {logDebug} from 'src/log'; import { - connectedChannelID, - connectedTeam, + channelIDForCurrentCall, + teamForCurrentCall, currentlyRinging, didNotifyForCall, didRingForCall, @@ -42,7 +42,7 @@ import {notificationSounds, sendDesktopNotificationToMe} from 'src/webapp_global export const useDismissJoin = (channelID: string, callID: string, onWidget = false) => { const store = useStore(); const dispatch = useDispatch(); - const connectedID = useSelector(connectedChannelID) || ''; + const connectedID = useSelector(channelIDForCurrentCall) || ''; const global = isDesktopApp(); const source = telemetrySource(onWidget); @@ -91,7 +91,7 @@ export const useDismissJoin = (channelID: string, callID: string, onWidget = fal }; export const useOnACallWithoutGlobalWidget = () => { - const connectedChannel = useSelector(connectedChannelID); + const connectedChannel = useSelector(channelIDForCurrentCall); return Boolean(connectedChannel && !shouldRenderDesktopWidget()); }; @@ -154,7 +154,7 @@ export const useRingingAndNotification = (call: IncomingCallNotification, onWidg const [shouldRing] = useNotificationSettings(call.channelID, currentUser); const currRinging = useSelector(currentlyRinging); const currRingingForThisCall = useSelector((state: GlobalState) => ringingForCall(state, call.callID)); - const connected = Boolean(useSelector(connectedChannelID)); + const connected = Boolean(useSelector(channelIDForCurrentCall)); useNotification(call); useEffect(() => { @@ -246,7 +246,7 @@ export const useGetCallerNameAndOthers = (call: IncomingCallNotification, splitA export const useOnChannelLinkClick = (call: IncomingCallNotification, onWidget = false) => { const dispatch = useDispatch(); const global = Boolean(isDesktopApp() && getCallsClient()); - const defaultTeam = useSelector(connectedTeam); + const defaultTeam = useSelector(teamForCurrentCall); const channel = useSelector((state: GlobalState) => getChannel(state, call.channelID)); let channelURL = useSelector((state: GlobalState) => getChannelURL(state, channel, channel.team_id)); const source = telemetrySource(onWidget); diff --git a/webapp/src/components/reaction_stream/reaction_stream.tsx b/webapp/src/components/reaction_stream/reaction_stream.tsx index 510083161..1463483ca 100644 --- a/webapp/src/components/reaction_stream/reaction_stream.tsx +++ b/webapp/src/components/reaction_stream/reaction_stream.tsx @@ -10,9 +10,9 @@ import styled, {css} from 'styled-components'; import {Emoji} from 'src/components/emoji/emoji'; import HandEmoji from 'src/components/icons/hand'; import { - idToProfileInConnectedChannel, - voiceReactions, - voiceUsersStatuses, + idToProfileInCurrentCall, + usersStatusesInCurrentCall, + reactionsInCurrentCall, } from 'src/selectors'; import {getUserDisplayName, split} from 'src/utils'; @@ -21,9 +21,9 @@ export const ReactionStream = () => { const {formatMessage, formatList} = useIntl(); const currentUserID = useSelector(getCurrentUserId); - const statuses = useSelector(voiceUsersStatuses); - const profileMap = useSelector(idToProfileInConnectedChannel); - const vReactions = useSelector(voiceReactions); + const statuses = useSelector(usersStatusesInCurrentCall); + const profileMap = useSelector(idToProfileInCurrentCall); + const vReactions = useSelector(reactionsInCurrentCall); const reversed = [...vReactions].reverse(); const reactions = reversed.map((reaction) => { diff --git a/webapp/src/components/screen_source_modal/component.tsx b/webapp/src/components/screen_source_modal/component.tsx index ac5d34bf7..bfa2e4f93 100644 --- a/webapp/src/components/screen_source_modal/component.tsx +++ b/webapp/src/components/screen_source_modal/component.tsx @@ -1,4 +1,3 @@ -import {Channel} from '@mattermost/types/channels'; import React, {CSSProperties} from 'react'; import {IntlShape} from 'react-intl'; @@ -9,7 +8,6 @@ import './component.scss'; interface Props { intl: IntlShape, - connectedChannel: Channel, show: boolean, hideScreenSourceModal: () => void, } diff --git a/webapp/src/components/screen_source_modal/index.ts b/webapp/src/components/screen_source_modal/index.ts index 2249dd035..0b3bdda0a 100644 --- a/webapp/src/components/screen_source_modal/index.ts +++ b/webapp/src/components/screen_source_modal/index.ts @@ -1,16 +1,14 @@ import {GlobalState} from '@mattermost/types/store'; -import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {connect} from 'react-redux'; import {bindActionCreators, Dispatch} from 'redux'; import {hideScreenSourceModal} from '../../actions'; -import {connectedChannelID, screenSourceModal} from '../../selectors'; +import {screenSourceModal} from '../../selectors'; import ScreenSourceModal from './component'; const mapStateToProps = (state: GlobalState) => { return { - connectedChannel: getChannel(state, connectedChannelID(state) || ''), show: screenSourceModal(state), }; }; diff --git a/webapp/src/components/switch_call_modal/component.tsx b/webapp/src/components/switch_call_modal/component.tsx index 9656d0a47..736d5e425 100644 --- a/webapp/src/components/switch_call_modal/component.tsx +++ b/webapp/src/components/switch_call_modal/component.tsx @@ -11,7 +11,7 @@ import './component.scss'; interface Props { intl: IntlShape, currentChannel: Channel, - connectedChannel: Channel, + connectedChannel?: Channel, currentDMUser: UserProfile | undefined, connectedDMUser: UserProfile | undefined, show: boolean, @@ -118,7 +118,7 @@ export default class SwitchCallModal extends React.PureComponent { render() { const {formatMessage} = this.props.intl; - if (!this.props.show) { + if (!this.props.show || !this.props.connectedChannel) { return null; } diff --git a/webapp/src/components/switch_call_modal/index.ts b/webapp/src/components/switch_call_modal/index.ts index 7fcae8c55..f446a3925 100644 --- a/webapp/src/components/switch_call_modal/index.ts +++ b/webapp/src/components/switch_call_modal/index.ts @@ -5,20 +5,20 @@ import {connect} from 'react-redux'; import {bindActionCreators, Dispatch} from 'redux'; import {dismissIncomingCallNotification, hideSwitchCallModal} from 'src/actions'; -import {connectedChannelID, switchCallModal, voiceChannelCallID} from 'src/selectors'; +import {switchCallModal, idForCallInChannel, channelForCurrentCall} from 'src/selectors'; import {isDMChannel, getUserIdFromDM} from 'src/utils'; import SwitchCallModal from './component'; const mapStateToProps = (state: GlobalState) => { const switchCallState = switchCallModal(state); - const connectedChannel = getChannel(state, connectedChannelID(state) || ''); - const targetCallID = voiceChannelCallID(state, switchCallState.targetID || '') || ''; + const connectedChan = channelForCurrentCall(state); + const targetCallID = idForCallInChannel(state, switchCallState.targetID || '') || ''; const currentChannel = switchCallState.targetID ? getChannel(state, switchCallState.targetID) : getChannel(state, getCurrentChannelId(state)); let connectedDMUser; - if (connectedChannel && isDMChannel(connectedChannel)) { - const otherID = getUserIdFromDM(connectedChannel.name, getCurrentUserId(state)); + if (connectedChan && isDMChannel(connectedChan)) { + const otherID = getUserIdFromDM(connectedChan.name, getCurrentUserId(state)); connectedDMUser = getUser(state, otherID); } @@ -30,7 +30,7 @@ const mapStateToProps = (state: GlobalState) => { return { show: switchCallModal(state).show, - connectedChannel, + connectedChannel: connectedChan, currentChannel, connectedDMUser, currentDMUser, diff --git a/webapp/src/index.tsx b/webapp/src/index.tsx index 655eec5cd..da22c2b1a 100644 --- a/webapp/src/index.tsx +++ b/webapp/src/index.tsx @@ -49,21 +49,21 @@ import {CallActions, CurrentCallData, CurrentCallDataDefault} from 'src/types/ty import { DESKTOP_WIDGET_CONNECTED, RECEIVED_CHANNEL_STATE, + USER_CONNECTED, + USERS_CONNECTED, + USERS_CONNECTED_STATES, + PROFILES_CONNECTED, + CALL_STATE, + USER_SCREEN_ON, + UNINIT, SHOW_SWITCH_CALL_MODAL, - VOICE_CHANNEL_CALL_HOST, - VOICE_CHANNEL_CALL_RECORDING_STATE, - VOICE_CHANNEL_CALL_START, - VOICE_CHANNEL_PROFILES_CONNECTED, - VOICE_CHANNEL_ROOT_POST, - VOICE_CHANNEL_UNINIT, - VOICE_CHANNEL_USER_CONNECTED, - VOICE_CHANNEL_USER_MUTED, - VOICE_CHANNEL_USER_RAISE_HAND, - VOICE_CHANNEL_USER_SCREEN_ON, - VOICE_CHANNEL_USER_UNMUTED, - VOICE_CHANNEL_USER_UNRAISE_HAND, - VOICE_CHANNEL_USERS_CONNECTED, - VOICE_CHANNEL_USERS_CONNECTED_STATES, + CALL_HOST, + CALL_RECORDING_STATE, + USER_MUTED, + USER_UNMUTED, + USER_RAISE_HAND, + USER_UNRAISE_HAND, + DISMISS_CALL, } from './action_types'; import CallsClient from './client'; import CallWidget from './components/call_widget'; @@ -81,21 +81,22 @@ import {logDebug, logErr} from './log'; import {pluginId} from './manifest'; import reducer from './reducers'; import { - callsConfig, - callsExplicitlyDisabled, - callsExplicitlyEnabled, - channelHasCall, - connectedChannelID, + channelIDForCurrentCall, + usersInCallInCurrentChannel, + usersInCallInChannel, + isLimitRestricted, + iceServers, + needsTURNCredentials, defaultEnabled, hasPermissionsToEnableCalls, - iceServers, isCloudStarter, - isLimitRestricted, - needsTURNCredentials, ringingEnabled, - voiceChannelCallStartAt, - voiceConnectedUsers, - voiceConnectedUsersInChannel, + hostChangeAtForCurrentCall, + callStartAtForCallInChannel, + callsConfig, + callsExplicitlyEnabled, + callsExplicitlyDisabled, + channelHasCall, } from './selectors'; import {JOIN_CALL, keyToAction} from './shortcuts'; import {PluginRegistry, Store} from './types/mattermost-webapp'; @@ -268,10 +269,10 @@ export default class Plugin { const connectToCall = async (channelId: string, teamId: string, title?: string, rootId?: string) => { try { - const users = voiceConnectedUsers(store.getState()); + const users = usersInCallInCurrentChannel(store.getState()); if (users && users.length > 0) { store.dispatch({ - type: VOICE_CHANNEL_PROFILES_CONNECTED, + type: PROFILES_CONNECTED, data: { profiles: await getProfilesByIds(store.getState(), users), channelId, @@ -282,15 +283,15 @@ export default class Plugin { logErr(err); } - if (!connectedChannelID(store.getState())) { + if (!channelIDForCurrentCall(store.getState())) { connectCall(channelId, title, rootId); // following the thread only on join. On call start // this is done in the call_start ws event handler. - if (voiceConnectedUsersInChannel(store.getState(), channelId).length > 0) { + if (usersInCallInChannel(store.getState(), channelId).length > 0) { followThread(store, channelId, teamId); } - } else if (connectedChannelID(store.getState()) !== channelId) { + } else if (channelIDForCurrentCall(store.getState()) !== channelId) { store.dispatch({ type: SHOW_SWITCH_CALL_MODAL, }); @@ -468,7 +469,7 @@ export default class Plugin { window.callsClient.on('mute', () => { store.dispatch({ - type: VOICE_CHANNEL_USER_MUTED, + type: USER_MUTED, data: { channelID: window.callsClient?.channelID, userID: getCurrentUserId(store.getState()), @@ -478,7 +479,7 @@ export default class Plugin { window.callsClient.on('unmute', () => { store.dispatch({ - type: VOICE_CHANNEL_USER_UNMUTED, + type: USER_UNMUTED, data: { channelID: window.callsClient?.channelID, userID: getCurrentUserId(store.getState()), @@ -488,7 +489,7 @@ export default class Plugin { window.callsClient.on('raise_hand', () => { store.dispatch({ - type: VOICE_CHANNEL_USER_RAISE_HAND, + type: USER_RAISE_HAND, data: { channelID: window.callsClient?.channelID, userID: getCurrentUserId(store.getState()), @@ -499,7 +500,7 @@ export default class Plugin { window.callsClient.on('lower_hand', () => { store.dispatch({ - type: VOICE_CHANNEL_USER_UNRAISE_HAND, + type: USER_UNRAISE_HAND, data: { channelID: window.callsClient?.channelID, userID: getCurrentUserId(store.getState()), @@ -585,22 +586,20 @@ export default class Plugin { for (let i = 0; i < data.length; i++) { actions.push({ - type: VOICE_CHANNEL_USERS_CONNECTED, + type: USERS_CONNECTED, data: { users: data[i].call?.users, channelID: data[i].channel_id, }, }); - if (!voiceChannelCallStartAt(store.getState(), data[i].channel_id)) { + if (!callStartAtForCallInChannel(store.getState(), data[i].channel_id)) { actions.push({ - type: VOICE_CHANNEL_CALL_START, + type: CALL_STATE, data: { ID: data[i].call?.id, channelID: data[i].channel_id, startAt: data[i].call?.start_at, ownerID: data[i].call?.owner_id, - hostID: data[i].call?.host_id, - dismissedNotification: data[i].call?.dismissed_notification || {}, }, }); @@ -610,6 +609,12 @@ export default class Plugin { if (dismissed) { const currentUserID = getCurrentUserId(store.getState()); if (Object.hasOwn(dismissed, currentUserID) && dismissed[currentUserID]) { + actions.push({ + type: DISMISS_CALL, + data: { + callID: data[i].call.id, + }, + }); continue; } } @@ -669,44 +674,49 @@ export default class Plugin { } actions.push({ - type: VOICE_CHANNEL_CALL_START, + type: CALL_STATE, data: { ID: call.id, channelID, startAt: call.start_at, ownerID: call.owner_id, - hostID: call.host_id, - dismissedNotification: call.dismissed_notification, + threadID: call.thread_id, }, }); - actions.push({ - type: VOICE_CHANNEL_USERS_CONNECTED, - data: { - users: call.users || [], - channelID, - }, - }); + const dismissed = call.dismissed_notification; + if (dismissed) { + const currentUserID = getCurrentUserId(store.getState()); + if (Object.hasOwn(dismissed, currentUserID) && dismissed[currentUserID]) { + actions.push({ + type: DISMISS_CALL, + data: { + callID: call.id, + }, + }); + } + } actions.push({ - type: VOICE_CHANNEL_ROOT_POST, + type: USERS_CONNECTED, data: { + users: call.users || [], channelID, - rootPost: call.thread_id, }, }); actions.push({ - type: VOICE_CHANNEL_CALL_HOST, + type: CALL_HOST, data: { channelID, hostID: call.host_id, + hostChangeAt: hostChangeAtForCurrentCall(store.getState()) || call.start_at, }, }); if (call.users && call.users.length > 0) { actions.push({ - type: VOICE_CHANNEL_PROFILES_CONNECTED, + type: PROFILES_CONNECTED, data: { profiles: await getProfilesByIds(store.getState(), call.users), channelID, @@ -715,7 +725,7 @@ export default class Plugin { } actions.push({ - type: VOICE_CHANNEL_CALL_RECORDING_STATE, + type: CALL_RECORDING_STATE, data: { callID: channelID, recState: call.recording, @@ -723,7 +733,7 @@ export default class Plugin { }); actions.push({ - type: VOICE_CHANNEL_USER_SCREEN_ON, + type: USER_SCREEN_ON, data: { channelID, userID: call.screen_sharing_id, @@ -737,7 +747,7 @@ export default class Plugin { userStates[users[i]] = {...states[i], id: users[i]}; } actions.push({ - type: VOICE_CHANNEL_USERS_CONNECTED_STATES, + type: USERS_CONNECTED_STATES, data: { states: userStates, channelID, @@ -779,7 +789,7 @@ export default class Plugin { const expandedID = getExpandedChannelID(); if (expandedID.length > 0) { actions.push({ - type: VOICE_CHANNEL_USER_CONNECTED, + type: USER_CONNECTED, data: { channelID: expandedID, userID: getCurrentUserId(store.getState()), @@ -799,7 +809,7 @@ export default class Plugin { } logDebug('resetting state'); store.dispatch({ - type: VOICE_CHANNEL_UNINIT, + type: UNINIT, }); }); @@ -809,7 +819,7 @@ export default class Plugin { if (!window.callsClient) { logDebug('resetting state'); store.dispatch({ - type: VOICE_CHANNEL_UNINIT, + type: UNINIT, }); } onActivate(); @@ -825,7 +835,7 @@ export default class Plugin { fetchChannelData(currChannelId).then((actions) => store.dispatch(batchActions(actions)), ); - if (currChannelId && Boolean(joinCallParam) && !connectedChannelID(store.getState())) { + if (currChannelId && Boolean(joinCallParam) && !channelIDForCurrentCall(store.getState())) { connectCall(currChannelId); } joinCallParam = ''; diff --git a/webapp/src/reducers.ts b/webapp/src/reducers.ts index af2d5b47a..990aba665 100644 --- a/webapp/src/reducers.ts +++ b/webapp/src/reducers.ts @@ -26,30 +26,29 @@ import { SHOW_EXPANDED_VIEW, SHOW_SCREEN_SOURCE_MODAL, SHOW_SWITCH_CALL_MODAL, - VOICE_CHANNEL_CALL_END, - VOICE_CHANNEL_CALL_HOST, - VOICE_CHANNEL_CALL_REC_PROMPT_DISMISSED, - VOICE_CHANNEL_CALL_RECORDING_STATE, - VOICE_CHANNEL_CALL_START, - VOICE_CHANNEL_PROFILE_CONNECTED, - VOICE_CHANNEL_PROFILES_CONNECTED, - VOICE_CHANNEL_ROOT_POST, - VOICE_CHANNEL_UNINIT, - VOICE_CHANNEL_USER_CONNECTED, - VOICE_CHANNEL_USER_DISCONNECTED, - VOICE_CHANNEL_USER_MUTED, - VOICE_CHANNEL_USER_RAISE_HAND, - VOICE_CHANNEL_USER_REACTED, - VOICE_CHANNEL_USER_REACTED_TIMEOUT, - VOICE_CHANNEL_USER_SCREEN_OFF, - VOICE_CHANNEL_USER_SCREEN_ON, - VOICE_CHANNEL_USER_UNMUTED, - VOICE_CHANNEL_USER_UNRAISE_HAND, - VOICE_CHANNEL_USER_VOICE_OFF, - VOICE_CHANNEL_USER_VOICE_ON, - VOICE_CHANNEL_USERS_CONNECTED, - VOICE_CHANNEL_USERS_CONNECTED_STATES, - VOICE_CHANNEL_USER_JOINED_TIMEOUT, + CALL_END, + CALL_REC_PROMPT_DISMISSED, + CALL_RECORDING_STATE, + CALL_STATE, + CALL_HOST, + PROFILE_CONNECTED, + PROFILES_CONNECTED, + UNINIT, + USER_CONNECTED, + USER_DISCONNECTED, + USER_MUTED, + USER_RAISE_HAND, + USER_REACTED, + USER_REACTED_TIMEOUT, + USER_SCREEN_OFF, + USER_SCREEN_ON, + USER_UNMUTED, + USER_UNRAISE_HAND, + USER_VOICE_OFF, + USER_VOICE_ON, + USERS_CONNECTED, + USERS_CONNECTED_STATES, + USER_JOINED_TIMEOUT, RECORDINGS_ENABLED, ADD_INCOMING_CALL, REMOVE_INCOMING_CALL, @@ -60,12 +59,16 @@ import { DISMISS_CALL, } from './action_types'; -interface channelStateAction { - type: string, - data: ChannelState, +type channelsState = { + [channelID: string]: ChannelState; } -const channelState = (state: { [channelID: string]: ChannelState } = {}, action: channelStateAction) => { +type channelsStateAction = { + type: string; + data: ChannelState; +} + +const channels = (state: channelsState = {}, action: channelsStateAction) => { switch (action.type) { case RECEIVED_CHANNEL_STATE: return { @@ -77,30 +80,31 @@ const channelState = (state: { [channelID: string]: ChannelState } = {}, action: } }; -interface connectedProfilesState { +type profilesState = { [channelID: string]: UserProfile[], } -interface connectedProfilesAction { - type: string, +type profilesAction = { + type: string; data: { - channelID: string, - userID?: string, - profile?: UserProfile, - profiles?: UserProfile[] - }, + channelID: string; + userID?: string; + profile?: UserProfile; + profiles?: UserProfile[]; + }; } -const voiceConnectedProfiles = (state: connectedProfilesState = {}, action: connectedProfilesAction) => { +// Profiles (as in whole User objects) connected to calls. +const profiles = (state: profilesState = {}, action: profilesAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {}; - case VOICE_CHANNEL_PROFILES_CONNECTED: + case PROFILES_CONNECTED: return { ...state, [action.data.channelID]: action.data.profiles, }; - case VOICE_CHANNEL_PROFILE_CONNECTED: + case PROFILE_CONNECTED: if (!state[action.data.channelID]) { return { ...state, @@ -121,12 +125,12 @@ const voiceConnectedProfiles = (state: connectedProfilesState = {}, action: conn action.data.profile, ], }; - case VOICE_CHANNEL_USER_DISCONNECTED: + case USER_DISCONNECTED: return { ...state, [action.data.channelID]: state[action.data.channelID]?.filter((val) => val.id !== action.data.userID), }; - case VOICE_CHANNEL_CALL_END: + case CALL_END: return { ...state, [action.data.channelID]: [], @@ -136,24 +140,24 @@ const voiceConnectedProfiles = (state: connectedProfilesState = {}, action: conn } }; -interface connectedChannelsState { - [channelID: string]: string[], +export type usersState = { + [channelID: string]: string[]; } -interface connectedChannelsAction { - type: string, +type usersAction = { + type: string; data: { - channelID: string, - userID?: string, - users?: string[], - }, + channelID: string; + userID?: string; + users?: string[]; + }; } -const voiceConnectedChannels = (state: connectedChannelsState = {}, action: connectedChannelsAction) => { +const users = (state: usersState = {}, action: usersAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {}; - case VOICE_CHANNEL_USER_CONNECTED: + case USER_CONNECTED: if (!state[action.data.channelID]) { return { ...state, @@ -167,17 +171,17 @@ const voiceConnectedChannels = (state: connectedChannelsState = {}, action: conn action.data.userID, ], }; - case VOICE_CHANNEL_USER_DISCONNECTED: + case USER_DISCONNECTED: return { ...state, [action.data.channelID]: state[action.data.channelID]?.filter((val) => val !== action.data.userID), }; - case VOICE_CHANNEL_USERS_CONNECTED: + case USERS_CONNECTED: return { ...state, [action.data.channelID]: action.data.users, }; - case VOICE_CHANNEL_CALL_END: + case CALL_END: return { ...state, [action.data.channelID]: [], @@ -187,25 +191,37 @@ const voiceConnectedChannels = (state: connectedChannelsState = {}, action: conn } }; -const connectedChannelID = (state: string | null = null, action: { type: string, data: { channelID: string, currentUserID: string, userID: string } }) => { +type channelIDState = string | null; + +type channelIDAction = { + type: string; + data: { + channelID: string; + currentUserID: string; + userID: string; + }; +} + +// channelID is the channel ID of the call the current user is connected to. +const channelID = (state: channelIDState = null, action: channelIDAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return null; case DESKTOP_WIDGET_CONNECTED: return action.data.channelID; - case VOICE_CHANNEL_USER_CONNECTED: { + case USER_CONNECTED: { const callsClient = window.callsClient || window.opener?.callsClient; if (action.data.currentUserID === action.data.userID && callsClient?.channelID === action.data.channelID) { return action.data.channelID; } return state; } - case VOICE_CHANNEL_USER_DISCONNECTED: + case USER_DISCONNECTED: if (action.data.currentUserID === action.data.userID && state === action.data.channelID) { return null; } return state; - case VOICE_CHANNEL_CALL_END: + case CALL_END: if (state === action.data.channelID) { return null; } @@ -215,98 +231,28 @@ const connectedChannelID = (state: string | null = null, action: { type: string, } }; -export interface UsersStatusesState { +export type usersStatusesState = { [channelID: string]: { - [userID: string]: UserState, - }, + [userID: string]: UserState; + }; } -interface usersStatusesAction { - type: string, +type usersStatusesAction = { + type: string; data: { - channelID: string, - userID: string, - raised_hand?: number, - reaction?: Reaction, - states: { [userID: string]: UserState }, - }, -} - -interface userReactionsState { - [channelID: string]: { - reactions: Reaction[], + channelID: string; + userID: string; + raised_hand?: number; + reaction?: Reaction; + states: { [userID: string]: UserState }; }; } -const queueReactions = (state: Reaction[], reaction: Reaction) => { - const result = state?.length ? [...state] : []; - result.push(reaction); - if (result.length > MAX_NUM_REACTIONS_IN_REACTION_STREAM) { - result.shift(); - } - return result; -}; - -const removeReaction = (reactions: Reaction[], reaction: Reaction) => { - return reactions.filter((r) => r.user_id !== reaction.user_id || r.timestamp > reaction.timestamp); -}; - -const reactionStatus = (state: userReactionsState = {}, action: usersStatusesAction) => { - switch (action.type) { - case VOICE_CHANNEL_USER_REACTED: - if (action.data.reaction) { - if (!state[action.data.channelID]) { - return { - ...state, - [action.data.channelID]: {reactions: [action.data.reaction]}, - }; - } - return { - ...state, - [action.data.channelID]: { - reactions: queueReactions(state[action.data.channelID].reactions, action.data.reaction), - }, - }; - } - return state; - case VOICE_CHANNEL_USER_REACTED_TIMEOUT: - if (!state[action.data.channelID]?.reactions || !action.data.reaction) { - return state; - } - return { - ...state, - [action.data.channelID]: { - reactions: removeReaction( - state[action.data.channelID].reactions, - { - ...action.data.reaction, - user_id: action.data.userID, - }), - }, - }; - case VOICE_CHANNEL_USER_DISCONNECTED: - if (!state[action.data.channelID] || !state[action.data.channelID].reactions) { - return state; - } - - return { - ...state, - [action.data.channelID]: { - reactions: state[action.data.channelID].reactions.filter((r) => { - return r.user_id !== action.data.userID; - }), - }, - }; - default: - return state; - } -}; - -const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatusesAction) => { +const usersStatuses = (state: usersStatusesState = {}, action: usersStatusesAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {}; - case VOICE_CHANNEL_USER_CONNECTED: + case USER_CONNECTED: if (state[action.data.channelID]) { return { ...state, @@ -322,7 +268,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }; } return state; - case VOICE_CHANNEL_USER_DISCONNECTED: + case USER_DISCONNECTED: if (state[action.data.channelID]) { // eslint-disable-next-line const {[action.data.userID]: omit, ...res} = state[action.data.channelID]; @@ -332,12 +278,12 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }; } return state; - case VOICE_CHANNEL_USERS_CONNECTED_STATES: + case USERS_CONNECTED_STATES: return { ...state, [action.data.channelID]: action.data.states, }; - case VOICE_CHANNEL_USER_MUTED: + case USER_MUTED: if (!state[action.data.channelID]) { return { ...state, @@ -361,7 +307,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }, }, }; - case VOICE_CHANNEL_USER_UNMUTED: + case USER_UNMUTED: if (!state[action.data.channelID]) { return { ...state, @@ -385,7 +331,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }, }, }; - case VOICE_CHANNEL_USER_VOICE_ON: + case USER_VOICE_ON: if (!state[action.data.channelID]) { return { ...state, @@ -409,7 +355,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }, }, }; - case VOICE_CHANNEL_USER_VOICE_OFF: + case USER_VOICE_OFF: if (!state[action.data.channelID]) { return { ...state, @@ -433,7 +379,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }, }, }; - case VOICE_CHANNEL_USER_RAISE_HAND: + case USER_RAISE_HAND: if (!state[action.data.channelID]) { return { ...state, @@ -457,7 +403,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }, }, }; - case VOICE_CHANNEL_USER_UNRAISE_HAND: + case USER_UNRAISE_HAND: if (!state[action.data.channelID]) { return { ...state, @@ -481,7 +427,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }, }, }; - case VOICE_CHANNEL_USER_REACTED: + case USER_REACTED: if (!state[action.data.channelID]) { return { ...state, @@ -506,7 +452,7 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse }, }, }; - case VOICE_CHANNEL_USER_REACTED_TIMEOUT: { + case USER_REACTED_TIMEOUT: { const storedReaction = state[action.data.channelID]?.[action.data.userID]?.reaction; if (!storedReaction || !action.data.reaction) { return state; @@ -530,36 +476,110 @@ const voiceUsersStatuses = (state: UsersStatusesState = {}, action: usersStatuse } }; -type callRecordingStateAction = { - type: string, - data: { - callID: string, - recState: CallRecordingState | null, - }, +export type usersReactionsState = { + [channelID: string]: { + reactions: Reaction[]; + }; +} + +const queueReactions = (state: Reaction[], reaction: Reaction) => { + const result = state?.length ? [...state] : []; + result.push(reaction); + if (result.length > MAX_NUM_REACTIONS_IN_REACTION_STREAM) { + result.shift(); + } + return result; +}; + +const removeReaction = (reactions: Reaction[], reaction: Reaction) => { + return reactions.filter((r) => r.user_id !== reaction.user_id || r.timestamp > reaction.timestamp); +}; + +const reactions = (state: usersReactionsState = {}, action: usersStatusesAction) => { + switch (action.type) { + case USER_REACTED: + if (action.data.reaction) { + if (!state[action.data.channelID]) { + return { + ...state, + [action.data.channelID]: {reactions: [action.data.reaction]}, + }; + } + return { + ...state, + [action.data.channelID]: { + reactions: queueReactions(state[action.data.channelID].reactions, action.data.reaction), + }, + }; + } + return state; + case USER_REACTED_TIMEOUT: + if (!state[action.data.channelID]?.reactions || !action.data.reaction) { + return state; + } + return { + ...state, + [action.data.channelID]: { + reactions: removeReaction( + state[action.data.channelID].reactions, + { + ...action.data.reaction, + user_id: action.data.userID, + }), + }, + }; + case USER_DISCONNECTED: + if (!state[action.data.channelID] || !state[action.data.channelID].reactions) { + return state; + } + + return { + ...state, + [action.data.channelID]: { + reactions: state[action.data.channelID].reactions.filter((r) => { + return r.user_id !== action.data.userID; + }), + }, + }; + default: + return state; + } +}; + +export type callsRecordingsState = { + [callID: string]: CallRecordingState; } type userDisconnectedAction = { - type: string, + type: string; + data: { + channelID: string; + userID: string; + currentUserID: string; + }; +} + +type recordingStateAction = { + type: string; data: { - channelID: string, - userID: string, - currentUserID: string, - }, + callID: string; + recState: CallRecordingState | null; + }; } type disclaimerDismissedAction = { - type: string, + type: string; data: { - callID: string, - dismissedAt: number, - } + callID: string; + dismissedAt: number; + }; } -const callsRecordings = (state: { [callID: string]: CallRecordingState } = {}, action: callRecordingStateAction | userDisconnectedAction | disclaimerDismissedAction) => { +const recordings = (state: callsRecordingsState = {}, action: recordingStateAction | userDisconnectedAction | disclaimerDismissedAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {}; - case VOICE_CHANNEL_USER_DISCONNECTED: { + case USER_DISCONNECTED: { const theAction = action as userDisconnectedAction; if (theAction.data.currentUserID === theAction.data.userID) { const nextState = {...state}; @@ -568,8 +588,8 @@ const callsRecordings = (state: { [callID: string]: CallRecordingState } = {}, a } return state; } - case VOICE_CHANNEL_CALL_RECORDING_STATE: { - const theAction = action as callRecordingStateAction; + case CALL_RECORDING_STATE: { + const theAction = action as recordingStateAction; return { ...state, [theAction.data.callID]: { @@ -578,7 +598,7 @@ const callsRecordings = (state: { [callID: string]: CallRecordingState } = {}, a }, }; } - case VOICE_CHANNEL_CALL_REC_PROMPT_DISMISSED: { + case CALL_REC_PROMPT_DISMISSED: { const theAction = action as disclaimerDismissedAction; return { ...state, @@ -593,41 +613,34 @@ const callsRecordings = (state: { [callID: string]: CallRecordingState } = {}, a } }; -export interface callState { - ID?: string, - channelID: string, - startAt?: number, - ownerID?: string, - hostID: string, - hostChangeAt?: number, - dismissedNotification: { [userID: string]: boolean }, +// callState should only hold immutable data, meaning those +// fields that don't change for the whole duration of a call. +export type callState = { + ID: string; + startAt: number; + channelID: string; + threadID: string; + ownerID: string; +} + +type callStateAction = { + type: string; + data: callState; } -export interface callStateAction { - type: string, - data: callState, +type callsState = { + [channelID: string]: callState; } -const voiceChannelCalls = (state: { [channelID: string]: callState } = {}, action: callStateAction) => { +const calls = (state: callsState = {}, action: callStateAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {}; - case VOICE_CHANNEL_CALL_HOST: - return { - ...state, - [action.data.channelID]: { - ...state[action.data.channelID], - hostID: action.data.hostID, - hostChangeAt: action.data.hostChangeAt || state[action.data.channelID].hostChangeAt, - }, - }; - case VOICE_CHANNEL_CALL_START: + case CALL_STATE: return { ...state, [action.data.channelID]: { ...action.data, - hostChangeAt: action.data.startAt, - dismissedNotification: action.data.dismissedNotification, }, }; default: @@ -635,28 +648,61 @@ const voiceChannelCalls = (state: { [channelID: string]: callState } = {}, actio } }; -const voiceChannelRootPost = (state: { [channelID: string]: string } = {}, action: { type: string, data: { channelID: string, rootPost: string } }) => { +export type hostsState = { + [channelID: string]: { + hostID: string; + hostChangeAt?: number; + }; +} + +type hostsStateAction = { + type: string; + data: { + channelID: string; + hostID: string; + hostChangeAt: number; + }; +} + +const hosts = (state: hostsState = {}, action: hostsStateAction) => { switch (action.type) { - case VOICE_CHANNEL_ROOT_POST: + case UNINIT: + return {}; + case CALL_HOST: return { ...state, - [action.data.channelID]: action.data.rootPost, + [action.data.channelID]: { + hostID: action.data.hostID, + hostChangeAt: action.data.hostChangeAt, + }, }; default: return state; } }; -const voiceChannelScreenSharingID = (state: { [channelID: string]: string } = {}, action: { type: string, data: { channelID: string, userID?: string } }) => { +export type screenSharingIDsState = { + [channelID: string]: string; +} + +type screenSharingIDAction = { + type: string; + data: { + channelID: string; + userID?: string; + } +} + +const screenSharingIDs = (state: screenSharingIDsState = {}, action: screenSharingIDAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {}; - case VOICE_CHANNEL_USER_SCREEN_ON: + case USER_SCREEN_ON: return { ...state, [action.data.channelID]: action.data.userID, }; - case VOICE_CHANNEL_USER_DISCONNECTED: { + case USER_DISCONNECTED: { // If the user who disconnected matches the one sharing we // want to fallthrough and clear the state. if (action.data.userID !== state[action.data.channelID]) { @@ -664,8 +710,8 @@ const voiceChannelScreenSharingID = (state: { [channelID: string]: string } = {} } } // eslint-disable-next-line no-fallthrough - case VOICE_CHANNEL_CALL_END: - case VOICE_CHANNEL_USER_SCREEN_OFF: + case CALL_END: + case USER_SCREEN_OFF: return { ...state, [action.data.channelID]: '', @@ -677,7 +723,7 @@ const voiceChannelScreenSharingID = (state: { [channelID: string]: string } = {} const expandedView = (state = false, action: { type: string }) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return false; case SHOW_EXPANDED_VIEW: return true; @@ -693,7 +739,7 @@ const switchCallModal = (state = { targetID: '', }, action: { type: string, data?: { targetID: string } }) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {show: false, targetID: ''}; case SHOW_SWITCH_CALL_MODAL: return {show: true, targetID: action.data?.targetID}; @@ -720,7 +766,7 @@ const endCallModal = (state = { const screenSourceModal = (state = false, action: { type: string }) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return false; case SHOW_SCREEN_SOURCE_MODAL: return true; @@ -760,15 +806,15 @@ const callsUserPreferences = (state = CallsUserPreferencesDefault, action: { typ } }; -interface recentlyJoinedUsersState { - [channelID: string]: string[], +export type recentlyJoinedUsersState = { + [channelID: string]: string[]; } -const recentlyJoinedUsers = (state: recentlyJoinedUsersState = {}, action: connectedChannelsAction) => { +const recentlyJoinedUsers = (state: recentlyJoinedUsersState = {}, action: usersAction) => { switch (action.type) { - case VOICE_CHANNEL_UNINIT: + case UNINIT: return {}; - case VOICE_CHANNEL_USER_CONNECTED: + case USER_CONNECTED: if (!state[action.data.channelID]) { return { ...state, @@ -782,17 +828,17 @@ const recentlyJoinedUsers = (state: recentlyJoinedUsersState = {}, action: conne action.data.userID, ], }; - case VOICE_CHANNEL_USER_DISCONNECTED: + case USER_DISCONNECTED: return { ...state, [action.data.channelID]: state[action.data.channelID]?.filter((val) => val !== action.data.userID), }; - case VOICE_CHANNEL_CALL_END: + case CALL_END: return { ...state, [action.data.channelID]: [], }; - case VOICE_CHANNEL_USER_JOINED_TIMEOUT: + case USER_JOINED_TIMEOUT: return { ...state, [action.data.channelID]: state[action.data.channelID]?.filter((val) => val !== action.data.userID), @@ -810,7 +856,7 @@ type IncomingCallAction = { callerID: string; startAt: number; type: ChannelType; - }, + }; }; const incomingCalls = (state: IncomingCallNotification[] = [], action: IncomingCallAction) => { @@ -828,7 +874,7 @@ type RingNotifyForCallsAction = { type: string; data: { callID: string; - } + }; } const ringingForCalls = (state: { [callID: string]: boolean } = {}, action: RingNotifyForCallsAction) => { @@ -885,23 +931,23 @@ const dismissedCalls = (state: { [callID: string]: boolean } = {}, action: RingN }; export default combineReducers({ - channelState, - voiceConnectedChannels, - connectedChannelID, - voiceConnectedProfiles, - reactionStatus, - voiceUsersStatuses, - voiceChannelCalls, - voiceChannelScreenSharingID, + channels, + users, + channelID, + profiles, + reactions, + usersStatuses, + calls, + hosts, + screenSharingIDs, expandedView, switchCallModal, endCallModal, screenSourceModal, - voiceChannelRootPost, callsConfig, rtcdEnabled, callsUserPreferences, - callsRecordings, + recordings, recentlyJoinedUsers, incomingCalls, ringingForCalls, diff --git a/webapp/src/selectors.ts b/webapp/src/selectors.ts index f0679c3ab..83935f948 100644 --- a/webapp/src/selectors.ts +++ b/webapp/src/selectors.ts @@ -26,7 +26,16 @@ import { import {displayUsername} from 'mattermost-redux/utils/user_utils'; import {createSelector} from 'reselect'; -import {callState} from 'src/reducers'; +import { + callState, + usersState, + usersStatusesState, + usersReactionsState, + hostsState, + screenSharingIDsState, + callsRecordingsState, + recentlyJoinedUsersState, +} from 'src/reducers'; import {CallRecordingReduxState, CallsUserPreferences, ChannelState, IncomingCallNotification} from 'src/types/types'; import {getChannelURL} from 'src/utils'; @@ -35,61 +44,60 @@ import {pluginId} from './manifest'; //@ts-ignore GlobalState is not complete const pluginState = (state: GlobalState) => state['plugins-' + pluginId] || {}; -export const voiceConnectedChannels = (state: GlobalState): { [channelId: string]: string[] } => - pluginState(state).voiceConnectedChannels; +const usersInCalls = (state: GlobalState): usersState => + pluginState(state).users; -export const voiceConnectedCurrentChannel: (state: GlobalState) => string[] = +export const usersInCallInCurrentChannel: (state: GlobalState) => string[] = createSelector( - 'voiceConnectedCurrentChannel', - voiceConnectedChannels, + 'usersInCallInCurrentChannel', + usersInCalls, getCurrentChannelId, - (channels, currChannelId) => channels[currChannelId], + (users, currChannelId) => users[currChannelId] || [], ); -export const voiceConnectedUsers = (state: GlobalState): string[] => { - const currentChannelID = getCurrentChannelId(state); - const channels = voiceConnectedChannels(state); - if (channels && channels[currentChannelID]) { - return channels[currentChannelID]; - } - return []; -}; - -const numCurrentVoiceConnectedUsers = (state: GlobalState) => - voiceConnectedUsers(state).length; +const numUsersInCallInCurrentChannel = (state: GlobalState) => + usersInCallInCurrentChannel(state).length; -export const voiceConnectedUsersInChannel = (state: GlobalState, channelId: string): string[] => { - const channels = voiceConnectedChannels(state); - if (channels && channels[channelId]) { - return channels[channelId]; +export const usersInCallInChannel = (state: GlobalState, channelId: string): string[] => { + const users = usersInCalls(state); + if (users && users[channelId]) { + return users[channelId]; } return []; }; export const channelHasCall = (state: GlobalState, channelId: string): boolean => { - const users = voiceConnectedUsersInChannel(state, channelId); + const users = usersInCallInChannel(state, channelId); return users && users.length > 0; }; -export const connectedChannelID = (state: GlobalState): string | null => - pluginState(state).connectedChannelID; +export const channelIDForCurrentCall = (state: GlobalState): string => + pluginState(state).channelID || ''; -export const connectedChannel: (state: GlobalState) => Channel | null = +export const channelForCurrentCall: (state: GlobalState) => Channel | undefined = createSelector( - 'connectedChannel', + 'channelForCurrentCall', getAllChannels, - connectedChannelID, - (channels, id) => channels[id || ''], + channelIDForCurrentCall, + (channels, id) => channels[id], ); -export const connectedCallID = (state: GlobalState): string | undefined => - pluginState(state).voiceChannelCalls[pluginState(state).connectedChannelID]?.ID; +export const calls = (state: GlobalState): { [channelID: string]: callState} => + pluginState(state).calls; -export const connectedTeam: (state: GlobalState) => Team | null = +export const idForCurrentCall: (state: GlobalState) => string | undefined = createSelector( - 'connectedTeam', + 'idForCurrentCall', + calls, + channelIDForCurrentCall, + (callsStates, channelID) => callsStates[channelID]?.ID, + ); + +export const teamForCurrentCall: (state: GlobalState) => Team | null = + createSelector( + 'teamForCurrentCall', getTeams, - connectedChannel, + channelForCurrentCall, getCurrentTeamId, (teams, channel, currentTeamID) => { const teamID = channel?.team_id || currentTeamID; @@ -97,137 +105,182 @@ export const connectedTeam: (state: GlobalState) => Team | null = }, ); -const numUsersInConnectedChannel = (state: GlobalState) => { - const connectedChannelId = connectedChannelID(state) || ''; - const connectedChannels = voiceConnectedChannels(state); - return connectedChannels[connectedChannelId]?.length || 0; -}; +const numUsersInCurrentCall : (state: GlobalState) => number = + createSelector( + 'numUsersInCurrentCall', + channelIDForCurrentCall, + usersInCalls, + (channelID, users) => { + return users[String(channelID)]?.length || 0; + }, + ); -export const voiceConnectedProfiles = (state: GlobalState): UserProfile[] => { - if (!pluginState(state).voiceConnectedProfiles) { - return []; - } - return pluginState(state).voiceConnectedProfiles[connectedChannelID(state) || ''] || []; -}; +const profilesInCalls = (state: GlobalState) => pluginState(state).profiles; -// idToProfileInCurrentChannel creates an id->UserProfile object for the currently connected channel. -export const idToProfileInConnectedChannel: (state: GlobalState) => { [id: string]: UserProfile } = +export const profilesInCurrentCall : (state: GlobalState) => UserProfile[] = createSelector( - 'idToProfileInCurrentChannel', - voiceConnectedProfiles, + 'profilesInCurrentCall', + profilesInCalls, + channelIDForCurrentCall, + (profiles, channelID) => profiles[channelID] || [], + ); + +export const profilesInCallInCurrentChannel: (state: GlobalState) => UserProfile[] = + createSelector( + 'profilesInCallInCurrentChannel', + profilesInCalls, + getCurrentChannelId, + (profiles, currChannelId) => profiles[currChannelId] || [], + ); + +// idToProfileInCurrentCall creates an id->UserProfile object for the currently connected call. +export const idToProfileInCurrentCall: (state: GlobalState) => { [id: string]: UserProfile } = + createSelector( + 'idToProfileInCurrentCall', + profilesInCurrentCall, (profiles) => makeIdToObject(profiles), ); -export const voiceConnectedProfilesInChannel: (state: GlobalState, channelId: string) => UserProfile[] = +export const profilesInCallInChannel: (state: GlobalState, channelId: string) => UserProfile[] = (state, channelID) => { - if (!pluginState(state).voiceConnectedProfiles) { - return []; - } - return pluginState(state).voiceConnectedProfiles[channelID] || []; + return profilesInCalls(state)[channelID] || []; }; -const voiceConnectedProfilesAllChannels: (state: GlobalState) => { [channelID: string]: UserProfile[] | undefined } = - (state) => pluginState(state).voiceConnectedProfiles; +const usersStatusesInCalls = (state: GlobalState): usersStatusesState => { + return pluginState(state).usersStatuses; +}; -export const voiceProfilesInCurrentChannel: (state: GlobalState) => UserProfile[] = +export const usersStatusesInCurrentCall: (state: GlobalState) => { [id: string]: UserState } = createSelector( - 'voiceProfilesInCurrentChannel', - voiceConnectedProfilesAllChannels, - getCurrentChannelId, - (channelToProfiles, currChannelId) => channelToProfiles[currChannelId] || [], + 'usersStatusesInCurrentCall', + usersStatusesInCalls, + channelIDForCurrentCall, + (statuses, channelID) => statuses[channelID] || {}, ); -export const voiceUsersStatuses = (state: GlobalState): { [id: string]: UserState } => { - return pluginState(state).voiceUsersStatuses[connectedChannelID(state) || ''] || {}; +const reactionsInCalls = (state: GlobalState): usersReactionsState => { + return pluginState(state).reactions; }; -export const voiceReactions = (state: GlobalState): Reaction[] => { - return pluginState(state).reactionStatus[connectedChannelID(state) || '']?.reactions || []; -}; - -export const voiceUsersStatusesInChannel = (state: GlobalState, channelID: string) => { - return pluginState(state).voiceUsersStatuses[channelID] || {}; -}; +export const reactionsInCurrentCall: (state: GlobalState) => Reaction[] = + createSelector( + 'reactionsInCurrentCall', + reactionsInCalls, + channelIDForCurrentCall, + (reactions, channelID) => reactions[channelID]?.reactions || [], + ); -export const voiceChannelCallStartAt = (state: GlobalState, channelID: string): number | undefined => { - return pluginState(state).voiceChannelCalls[channelID]?.startAt; +export const callStartAtForCallInChannel = (state: GlobalState, channelID: string): number => { + return pluginState(state).calls[channelID]?.startAt || 0; }; -export const voiceChannelCalls = (state: GlobalState): { [channelID: string]: callState} => - pluginState(state).voiceChannelCalls; +export const callStartAtForCurrentCall: (state: GlobalState) => number = + createSelector( + 'callStartAtForCurrentCall', + calls, + channelIDForCurrentCall, + (callsStates, channelID) => callsStates[channelID]?.startAt || + window.callsClient?.initTime || + window.opener.callsClient?.initTime || 0, + ); -export const voiceChannelCallInCurrentChannel: (state: GlobalState) => callState = +export const callInCurrentChannel: (state: GlobalState) => callState = createSelector( - 'callStartAtInCurrentChannel', - voiceChannelCalls, + 'callInCurrentChannel', + calls, getCurrentChannelId, - (calls, currChannelId) => calls[currChannelId], + (callsStates, currChannelId) => callsStates[currChannelId], ); -export const voiceChannelCallID = (state: GlobalState, channelID: string): string | undefined => { - return pluginState(state).voiceChannelCalls[channelID]?.ID; +export const idForCallInChannel = (state: GlobalState, channelID: string): string | undefined => { + return pluginState(state).calls[channelID]?.ID; }; -export const voiceChannelCallOwnerID = (state: GlobalState, channelID: string): string | undefined => { - return pluginState(state).voiceChannelCalls[channelID]?.ownerID; +export const callOwnerIDForCallInChannel = (state: GlobalState, channelID: string): string | undefined => { + return pluginState(state).calls[channelID]?.ownerID; }; -export const voiceChannelCallHostID = (state: GlobalState, channelID: string) => { - return pluginState(state).voiceChannelCalls[channelID]?.hostID; +const hostsInCalls = (state: GlobalState): hostsState => { + return pluginState(state).hosts; }; -export const voiceChannelCallHostChangeAt = (state: GlobalState, channelID: string) => { - return pluginState(state).voiceChannelCalls[channelID]?.hostChangeAt; -}; +export const hostIDForCurrentCall: (state: GlobalState) => string = + createSelector( + 'hostIDForCurrentCall', + hostsInCalls, + channelIDForCurrentCall, + (hosts, channelID) => hosts[channelID]?.hostID || '', + ); -export const voiceChannelCallDismissedNotification = (state: GlobalState, channelID: string) => { - const dismissed: { [userID: string]: boolean } | undefined = pluginState(state).voiceChannelCalls[channelID]?.dismissedNotification; - if (!dismissed) { - return false; - } - const currentUserID = getCurrentUserId(state); - return Object.hasOwn(dismissed, currentUserID) ? dismissed[currentUserID] : false; -}; +export const hostChangeAtForCurrentCall: (state: GlobalState) => number = + createSelector( + 'hostChangeAtForCurrentCall', + hostsInCalls, + channelIDForCurrentCall, + (hosts, channelID) => hosts[channelID]?.hostChangeAt || 0, + ); -export const voiceChannelScreenSharingID = (state: GlobalState, channelID: string): string | undefined => { - return pluginState(state).voiceChannelScreenSharingID[channelID]; +export const callDismissedNotification = (state: GlobalState, channelID: string) => { + return Boolean(pluginState(state).dismissedCalls[channelID]); }; -export const callRecording = (state: GlobalState, callID: string): CallRecordingReduxState => { - return pluginState(state).callsRecordings[callID]; +const screenSharingIDsForCalls = (state: GlobalState): screenSharingIDsState => { + return pluginState(state).screenSharingIDs; }; -export const isRecording = (state: GlobalState, callID: string): boolean => { - const recording = callRecording(state, callID); - if (!recording) { - return false; - } - - // Toggle wise (start/stop) we don't care whether the recording job is actually running. - // We should be able to stop a recording even during the initialization phase. +export const screenSharingIDForCurrentCall: (state: GlobalState) => string = + createSelector( + 'screenSharingIDForCurrentCall', + screenSharingIDsForCalls, + channelIDForCurrentCall, + (ids, channelID) => ids[channelID] || '', + ); - return recording.init_at > recording.end_at; +export const callThreadIDForCallInChannel = (state: GlobalState, channelID: string) => { + return pluginState(state).calls[channelID]?.threadID || ''; }; -export const expandedView = (state: GlobalState) => { - return pluginState(state).expandedView; +const recordingsForCalls = (state: GlobalState): callsRecordingsState => { + return pluginState(state).recordings; }; -export const switchCallModal = (state: GlobalState) => { - return pluginState(state).switchCallModal; -}; +export const recordingForCurrentCall: (state: GlobalState) => CallRecordingReduxState = + createSelector( + 'recordingForCurrentCall', + recordingsForCalls, + channelIDForCurrentCall, + (recordings, channelID) => recordings[channelID] || {}, + ); -export const screenSourceModal = (state: GlobalState) => { - return pluginState(state).screenSourceModal; +const recentlyJoinedUsersInCalls = (state: GlobalState): recentlyJoinedUsersState => { + return pluginState(state).recentlyJoinedUsers; }; -export const voiceChannelRootPost = (state: GlobalState, channelID: string) => { - return pluginState(state).voiceChannelRootPost[channelID]; -}; +export const recentlyJoinedUsersInCurrentCall: (state: GlobalState) => string[] = + createSelector( + 'recentlyJoinedUsersInCurrentCall', + recentlyJoinedUsersInCalls, + channelIDForCurrentCall, + (users, channelID) => users[channelID] || [], + ); -export const recentlyJoinedUsers = (state: GlobalState, channelID: string): string[] => { - return pluginState(state).recentlyJoinedUsers[channelID] || []; -}; +export const isRecordingInCurrentCall: (state: GlobalState) => boolean = + createSelector( + 'isRecordingInCurrentCall', + recordingsForCalls, + channelIDForCurrentCall, + (recordings, channelID) => { + const recording = recordings[channelID]; + if (!recording) { + return false; + } + + // Toggle wise (start/stop) we don't care whether the recording job is actually running. + // We should be able to stop a recording even during the initialization phase. + + return recording.init_at > recording.end_at; + }, + ); export const incomingCalls = (state: GlobalState): IncomingCallNotification[] => pluginState(state).incomingCalls; @@ -236,7 +289,7 @@ export const sortedIncomingCalls: (state: GlobalState) => IncomingCallNotificati createSelector( 'sortedIncomingCalls', incomingCalls, - (calls) => [...calls].sort((a, b) => b.startAt - a.startAt), + (callsStates) => [...callsStates].sort((a, b) => b.startAt - a.startAt), ); export const dismissedCalls = (state: GlobalState): { [callID: string]: boolean } => @@ -246,7 +299,7 @@ export const dismissedCallForCurrentChannel: (state: GlobalState) => boolean = createSelector( 'dismissedCallForCurrentChannel', dismissedCalls, - voiceChannelCallInCurrentChannel, + callInCurrentChannel, (dismissed, call) => Boolean(dismissed[call?.ID || '']), ); @@ -287,7 +340,7 @@ export const needsTURNCredentials = (state: GlobalState) => callsConfig(state).NeedsTURNCredentials; export const isLimitRestricted = (state: GlobalState): boolean => { - const numCurrentUsers = numCurrentVoiceConnectedUsers(state); + const numCurrentUsers = numUsersInCallInCurrentChannel(state); const max = maxParticipants(state); return max > 0 && numCurrentUsers >= max; }; @@ -311,7 +364,7 @@ export const ringingEnabled = (state: GlobalState) => // Calls enabled/disabled logic // export const channelState = (state: GlobalState, channelId: string): ChannelState => - pluginState(state).channelState[channelId]; + pluginState(state).channels[channelId]; export const callsExplicitlyEnabled = (state: GlobalState, channelId: string): boolean => Boolean(channelState(state, channelId)?.enabled); @@ -393,7 +446,7 @@ export const callsUserPreferences = (state: GlobalState): CallsUserPreferences = pluginState(state).callsUserPreferences; export const shouldPlayJoinUserSound = (state: GlobalState): boolean => - numUsersInConnectedChannel(state) < callsUserPreferences(state).joinSoundParticipantsThreshold; + numUsersInCurrentCall(state) < callsUserPreferences(state).joinSoundParticipantsThreshold; export const isOnPremNotEnterprise = (state: GlobalState): boolean => { const license = getLicense(state); @@ -403,7 +456,7 @@ export const isOnPremNotEnterprise = (state: GlobalState): boolean => { export const adminStats = (state: GlobalState) => state.entities.admin.analytics; -export const getChannelUrlAndDisplayName = (state: GlobalState, channel: Channel) => { +export const getChannelUrlAndDisplayName = (state: GlobalState, channel?: Channel) => { const currentUserID = getCurrentUserId(state); const teammateNameDisplaySetting = getTeammateNameDisplaySetting(state); const users = getUsers(state); @@ -441,3 +494,17 @@ export function makeIdToObject(arr: HasId[]) { return acc; }, {}); } + +// modals + +export const expandedView = (state: GlobalState) => { + return pluginState(state).expandedView; +}; + +export const switchCallModal = (state: GlobalState) => { + return pluginState(state).switchCallModal; +}; + +export const screenSourceModal = (state: GlobalState) => { + return pluginState(state).screenSourceModal; +}; diff --git a/webapp/src/slash_commands.tsx b/webapp/src/slash_commands.tsx index eeb093710..d7841888c 100644 --- a/webapp/src/slash_commands.tsx +++ b/webapp/src/slash_commands.tsx @@ -18,11 +18,11 @@ import * as Telemetry from 'src/types/telemetry'; import {logDebug} from './log'; import { - connectedChannelID, - voiceConnectedUsersInChannel, - voiceChannelCallOwnerID, - voiceChannelCallHostID, - isRecording, + channelIDForCurrentCall, + usersInCallInChannel, + callOwnerIDForCallInChannel, + hostIDForCurrentCall, + isRecordingInCurrentCall, } from './selectors'; import {Store} from './types/mattermost-webapp'; import {sendDesktopEvent, shouldRenderDesktopWidget} from './utils'; @@ -43,13 +43,13 @@ export default async function slashCommandsHandler(store: Store, joinCall: joinC return {message, args}; } - const connectedID = connectedChannelID(store.getState()); + const connectedID = channelIDForCurrentCall(store.getState()); switch (subCmd) { case 'join': case 'start': if (subCmd === 'start') { - if (voiceConnectedUsersInChannel(store.getState(), args.channel_id).length > 0) { + if (usersInCallInChannel(store.getState(), args.channel_id).length > 0) { store.dispatch(displayGenericErrorModal( defineMessage({defaultMessage: 'Unable to start call'}), defineMessage({defaultMessage: 'A call is already ongoing in the channel.'}), @@ -110,7 +110,7 @@ export default async function slashCommandsHandler(store: Store, joinCall: joinC )); return {}; case 'end': - if (voiceConnectedUsersInChannel(store.getState(), args.channel_id)?.length === 0) { + if (usersInCallInChannel(store.getState(), args.channel_id)?.length === 0) { store.dispatch(displayGenericErrorModal( defineMessage({defaultMessage: 'Unable to end the call'}), defineMessage({defaultMessage: 'There\'s no ongoing call in the channel.'}), @@ -119,7 +119,7 @@ export default async function slashCommandsHandler(store: Store, joinCall: joinC } if (!isCurrentUserSystemAdmin(store.getState()) && - getCurrentUserId(store.getState()) !== voiceChannelCallOwnerID(store.getState(), args.channel_id)) { + getCurrentUserId(store.getState()) !== callOwnerIDForCallInChannel(store.getState(), args.channel_id)) { store.dispatch(displayGenericErrorModal( defineMessage({defaultMessage: 'Unable to end the call'}), defineMessage({defaultMessage: 'You don\'t have permission to end the call. Please ask the call owner to end call.'}), @@ -177,7 +177,7 @@ export default async function slashCommandsHandler(store: Store, joinCall: joinC } const state = store.getState(); - const isHost = voiceChannelCallHostID(state, connectedID) === getCurrentUserId(state); + const isHost = hostIDForCurrentCall(state) === getCurrentUserId(state); if (fields[2] === 'start') { trackEvent(Telemetry.Event.StartRecording, Telemetry.Source.SlashCommand)(store.dispatch, store.getState); @@ -190,7 +190,7 @@ export default async function slashCommandsHandler(store: Store, joinCall: joinC return {}; } - if (isRecording(state, connectedID)) { + if (isRecordingInCurrentCall(state)) { store.dispatch(displayGenericErrorModal( startErrorTitle, defineMessage({defaultMessage: 'A recording is already in progress.'}), @@ -212,7 +212,7 @@ export default async function slashCommandsHandler(store: Store, joinCall: joinC return {}; } - if (!isRecording(state, connectedID)) { + if (!isRecordingInCurrentCall(state)) { store.dispatch(displayGenericErrorModal( stopErrorTitle, defineMessage({defaultMessage: 'No recording is in progress.'}), diff --git a/webapp/src/utils.ts b/webapp/src/utils.ts index 6900c3a4d..e9bac61a3 100644 --- a/webapp/src/utils.ts +++ b/webapp/src/utils.ts @@ -19,7 +19,7 @@ import CallsClient from 'src/client'; import {logDebug, logErr, logWarn} from './log'; import {pluginId} from './manifest'; -import {voiceChannelRootPost} from './selectors'; +import {callThreadIDForCallInChannel} from './selectors'; import JoinSelfSound from './sounds/join_self.mp3'; import JoinUserSound from './sounds/join_user.mp3'; import LeaveSelfSound from './sounds/leave_self.mp3'; @@ -345,7 +345,7 @@ export async function followThread(store: Store, channelID: string, teamID: stri logDebug('followThread: no team for channel'); return; } - const threadID = voiceChannelRootPost(store.getState(), channelID); + const threadID = callThreadIDForCallInChannel(store.getState(), channelID); if (threadID) { store.dispatch(setThreadFollow(getCurrentUserId(store.getState()), teamID, threadID, true)); } else { diff --git a/webapp/src/websocket_handlers.ts b/webapp/src/websocket_handlers.ts index 3a35b1e51..fb8bf6847 100644 --- a/webapp/src/websocket_handlers.ts +++ b/webapp/src/websocket_handlers.ts @@ -22,33 +22,32 @@ import {JOINED_USER_NOTIFICATION_TIMEOUT, REACTION_TIMEOUT_IN_REACTION_STREAM} f import {notificationSounds} from 'src/webapp_globals'; import { - VOICE_CHANNEL_USER_MUTED, - VOICE_CHANNEL_USER_UNMUTED, - VOICE_CHANNEL_USER_CONNECTED, - VOICE_CHANNEL_PROFILE_CONNECTED, - VOICE_CHANNEL_CALL_START, - VOICE_CHANNEL_CALL_END, - VOICE_CHANNEL_ROOT_POST, - VOICE_CHANNEL_USER_VOICE_ON, - VOICE_CHANNEL_USER_VOICE_OFF, - VOICE_CHANNEL_USER_SCREEN_ON, - VOICE_CHANNEL_USER_SCREEN_OFF, - VOICE_CHANNEL_USER_RAISE_HAND, - VOICE_CHANNEL_USER_UNRAISE_HAND, - VOICE_CHANNEL_USER_REACTED, - VOICE_CHANNEL_USER_REACTED_TIMEOUT, - VOICE_CHANNEL_CALL_HOST, - VOICE_CHANNEL_CALL_RECORDING_STATE, - VOICE_CHANNEL_USER_JOINED_TIMEOUT, + USER_MUTED, + USER_UNMUTED, + USER_CONNECTED, + PROFILE_CONNECTED, + CALL_STATE, + CALL_END, + USER_VOICE_ON, + USER_VOICE_OFF, + USER_SCREEN_ON, + USER_SCREEN_OFF, + USER_RAISE_HAND, + USER_UNRAISE_HAND, + USER_REACTED, + USER_REACTED_TIMEOUT, + CALL_HOST, + CALL_RECORDING_STATE, + USER_JOINED_TIMEOUT, DISMISS_CALL, } from './action_types'; import {logErr} from './log'; import { - connectedChannelID, - idToProfileInConnectedChannel, + channelIDForCurrentCall, + idToProfileInCurrentCall, ringingEnabled, shouldPlayJoinUserSound, - voiceChannelCalls, + calls, } from './selectors'; import {Store} from './types/mattermost-webapp'; import { @@ -61,19 +60,19 @@ import { export function handleCallEnd(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; - if (connectedChannelID(store.getState()) === channelID) { + if (channelIDForCurrentCall(store.getState()) === channelID) { window.callsClient?.disconnect(); } store.dispatch({ - type: VOICE_CHANNEL_CALL_END, + type: CALL_END, data: { channelID, }, }); if (ringingEnabled(store.getState())) { - const callID = voiceChannelCalls(store.getState())[channelID].ID || ''; + const callID = calls(store.getState())[channelID].ID || ''; store.dispatch(removeIncomingCallNotification(callID)); } } @@ -83,27 +82,29 @@ export function handleCallStart(store: Store, ev: WebSocketMessage { store.dispatch({ - type: VOICE_CHANNEL_USER_JOINED_TIMEOUT, + type: USER_JOINED_TIMEOUT, data: { channelID, userID, @@ -164,7 +165,7 @@ export async function handleUserConnected(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_MUTED, + type: USER_MUTED, data: { channelID, userID: ev.data.userID, @@ -189,7 +190,7 @@ export function handleUserMuted(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_UNMUTED, + type: USER_UNMUTED, data: { channelID, userID: ev.data.userID, @@ -200,7 +201,7 @@ export function handleUserUnmuted(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_VOICE_ON, + type: USER_VOICE_ON, data: { channelID, userID: ev.data.userID, @@ -211,7 +212,7 @@ export function handleUserVoiceOn(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_VOICE_OFF, + type: USER_VOICE_OFF, data: { channelID, userID: ev.data.userID, @@ -222,7 +223,7 @@ export function handleUserVoiceOff(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_SCREEN_ON, + type: USER_SCREEN_ON, data: { channelID, userID: ev.data.userID, @@ -233,7 +234,7 @@ export function handleUserScreenOn(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_SCREEN_OFF, + type: USER_SCREEN_OFF, data: { channelID, userID: ev.data.userID, @@ -244,7 +245,7 @@ export function handleUserScreenOff(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_RAISE_HAND, + type: USER_RAISE_HAND, data: { channelID, userID: ev.data.userID, @@ -256,7 +257,7 @@ export function handleUserRaisedHand(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; store.dispatch({ - type: VOICE_CHANNEL_USER_UNRAISE_HAND, + type: USER_UNRAISE_HAND, data: { channelID, userID: ev.data.userID, @@ -268,18 +269,18 @@ export function handleUserUnraisedHand(store: Store, ev: WebSocketMessage) { const channelID = ev.data.channelID || ev.broadcast.channel_id; - if (connectedChannelID(store.getState()) !== channelID) { + if (channelIDForCurrentCall(store.getState()) !== channelID) { return; } - const profiles = idToProfileInConnectedChannel(store.getState()); + const profiles = idToProfileInCurrentCall(store.getState()); const displayName = getUserDisplayName(profiles[ev.data.user_id]); const reaction: Reaction = { ...ev.data, displayName, }; store.dispatch({ - type: VOICE_CHANNEL_USER_REACTED, + type: USER_REACTED, data: { channelID, userID: ev.data.user_id, @@ -288,7 +289,7 @@ export function handleUserReaction(store: Store, ev: WebSocketMessage { store.dispatch({ - type: VOICE_CHANNEL_USER_REACTED_TIMEOUT, + type: USER_REACTED_TIMEOUT, data: { channelID, userID: ev.data.user_id, @@ -302,7 +303,7 @@ export function handleCallHostChanged(store: Store, ev: WebSocketMessage