UNPKG

@tencentcloud/roomkit-web-vue3

Version:

<h1 align="center"> TUIRoomKit</h1> Conference (TUIRoomKit) is a product suitable for multi-person audio and video conversation scenarios such as business meetings, webinars, and online education. By integrating this product, you can add room management,

616 lines (586 loc) 18.2 kB
import { computed, Ref, ref, reactive } from 'vue'; import { useI18n } from '../../../locales'; import { UserInfo, useRoomStore } from '../../../stores/room'; import useGetRoomEngine from '../../../hooks/useRoomEngine'; import { useBasicStore } from '../../../stores/basic'; import useMasterApplyControl from '../../../hooks/useMasterApplyControl'; import { TUIMediaDevice, TUIRole, TUIRequestCallbackType, TUIErrorCode, } from '@tencentcloud/tuiroom-engine-js'; import { storeToRefs } from 'pinia'; import { MESSAGE_DURATION } from '../../../constants/message'; import eventBus from '../../../hooks/useMitt'; import useMemberItemHooks from '../MemberItem/useMemberItemHooks'; import { roomService } from '../../../services'; import { isMobile } from '../../../utils/environment'; import { calculateByteLength } from '../../../utils/utils'; import { TUIToast, TOAST_TYPE, IconAudioOpen, IconVideoOpen, IconChatForbidden, IconTransferOwner, IconRevokeAdmin, IconSetAdmin, IconDenyOnStage, IconInviteOnStage, IconOnStage, IconOffStage, IconEditNameCard, IconKickOut, } from '@tencentcloud/uikit-base-component-vue3'; interface ObjectType { [key: string]: any; } type ActionType = 'transferOwner' | 'kickUser' | 'changeUserNameCard' | ''; export default function useMemberControl(props?: any) { const roomEngine = useGetRoomEngine(); const { t } = useI18n(); const basicStore = useBasicStore(); const roomStore = useRoomStore(); const isDialogVisible: Ref<boolean> = ref(false); const isShowInput: Ref<boolean> = ref(false); const editorInputEle = ref(); const editorInputEleContainer = ref(); const tempUserName = ref(''); const dialogData = reactive<{ title: string; content: string; confirmText: string; actionType: ActionType; showInput: boolean; isConfirmButtonDisable: boolean; }>({ title: '', content: '', confirmText: '', actionType: '' as ActionType, showInput: false, isConfirmButtonDisable: false, }); const kickOffDialogContent = computed(() => t('whether to kick sb off the room', { name: roomService.getDisplayName(props.userInfo), }) ); const transferOwnerTitle = computed(() => t('Transfer the roomOwner to sb', { name: roomService.getDisplayName(props.userInfo), }) ); const { isFreeSpeakMode, isSpeakAfterTakingSeatMode, localUser, isGeneralUser, anchorUserList, maxSeatCount, } = storeToRefs(roomStore); /** * Functions related to the Raise Your Hand function **/ const { agreeUserOnStage, denyUserOnStage, inviteUserOnStage, cancelInviteUserOnStage, kickUserOffStage, } = useMasterApplyControl(); const { isCanOperateMySelf } = useMemberItemHooks(props.userInfo); const isMe = computed(() => basicStore.userId === props.userInfo.userId); const isTargetUserAnchor = computed(() => props.userInfo.onSeat === true); const isTargetUserAudience = computed(() => props.userInfo.onSeat !== true); const controlList = computed(() => { if (isCanOperateMySelf.value) { return [changeUserNameCard.value]; } const agreeOrDenyStageList = props.userInfo.isUserApplyingToAnchor ? [agreeOnStage.value, denyOnStage.value] : []; const inviteStageList = isTargetUserAudience.value && !props.userInfo.isUserApplyingToAnchor ? [inviteOnStage.value] : []; const onStageControlList = isTargetUserAnchor.value ? [audioControl.value, videoControl.value, makeOffStage.value] : []; const controlListObj: ObjectType = { freeSpeak: { [TUIRole.kRoomOwner]: [ audioControl.value, videoControl.value, chatControl.value, setOrRevokeAdmin.value, transferOwner.value, kickUser.value, changeUserNameCard.value, ], [TUIRole.kAdministrator]: [ audioControl.value, videoControl.value, chatControl.value, changeUserNameCard.value, ], }, speakAfterTakeSeat: { [TUIRole.kRoomOwner]: [ ...inviteStageList, ...onStageControlList, ...agreeOrDenyStageList, setOrRevokeAdmin.value, transferOwner.value, chatControl.value, kickUser.value, changeUserNameCard.value, ], [TUIRole.kAdministrator]: [ ...inviteStageList, ...onStageControlList, ...agreeOrDenyStageList, chatControl.value, changeUserNameCard.value, ], }, }; if (isFreeSpeakMode.value) { return ( controlListObj.freeSpeak[ localUser.value.userRole as keyof ObjectType ] || [] ); } if (isSpeakAfterTakingSeatMode.value) { return ( controlListObj.speakAfterTakeSeat[ localUser.value.userRole as keyof ObjectType ] || [] ); } return []; }); const audioControl = computed(() => ({ key: 'audioControl', icon: IconAudioOpen, title: props.userInfo.hasAudioStream ? t('Mute') : t('Unmute'), func: muteUserAudio, })); const videoControl = computed(() => { const videoControlTitle = props.userInfo.hasVideoStream ? t('Disable video') : t('Enable video'); return { key: 'videoControl', icon: IconVideoOpen, title: videoControlTitle, func: muteUserVideo, }; }); const chatControl = computed(() => ({ key: 'chatControl', icon: IconChatForbidden, title: props.userInfo.isMessageDisabled ? t('Enable chat') : t('Disable chat'), func: disableUserChat, })); const transferOwner = computed(() => ({ key: 'transferOwner', icon: IconTransferOwner, title: t('Make host'), func: () => handleOpenDialog('transferOwner'), })); const setOrRevokeAdmin = computed(() => ({ key: 'setOrRevokeAdmin', icon: props.userInfo.userRole === TUIRole.kAdministrator ? IconRevokeAdmin : IconSetAdmin, title: props.userInfo.userRole === TUIRole.kAdministrator ? t('Remove administrator') : t('Set as administrator'), func: handleSetOrRevokeAdmin, })); const kickUser = computed(() => ({ key: 'kickUser', icon: IconKickOut, title: t('Kick out'), func: () => handleOpenDialog('kickUser'), })); const inviteOnStage = computed(() => ({ key: 'inviteOnStage', icon: props.userInfo.isInvitingUserToAnchor ? IconDenyOnStage : IconInviteOnStage, title: props.userInfo.isInvitingUserToAnchor ? t('Cancel stage') : t('Invite stage'), func: toggleInviteUserOnStage, })); const agreeOnStage = computed(() => ({ key: 'agreeOnStage', icon: IconOnStage, title: t('Agree to the stage'), func: agreeUserOnStage, })); const denyOnStage = computed(() => ({ key: 'denyOnStage', icon: IconDenyOnStage, title: t('Refuse stage'), func: denyUserOnStage, })); const makeOffStage = computed(() => ({ key: 'makeOffStage', icon: IconOffStage, title: t('Step down'), func: kickUserOffStage, })); const changeUserNameCard = computed(() => ({ key: 'changeUserNameCard', icon: IconEditNameCard, title: t('change name'), func: () => handleOpenDialog('changeUserNameCard'), })); /** * Invitation to the stage/uninvitation to the stage **/ async function toggleInviteUserOnStage(userInfo: UserInfo) { const { isInvitingUserToAnchor } = userInfo; if (isInvitingUserToAnchor) { cancelInviteUserOnStage(userInfo); } else { if (anchorUserList.value.length === maxSeatCount.value) { TUIToast({ type: TOAST_TYPE.WARNING, message: `${t('The stage is full')}`, duration: MESSAGE_DURATION.NORMAL, }); return; } inviteUserOnStage(userInfo); } } /** * Banning/Unbanning **/ async function muteUserAudio(userInfo: UserInfo) { if (userInfo.hasAudioStream) { await roomEngine.instance?.closeRemoteDeviceByAdmin({ userId: userInfo.userId, device: TUIMediaDevice.kMicrophone, }); } else { if (userInfo.isRequestingUserOpenMic) { TUIToast({ type: TOAST_TYPE.INFO, message: `${t('An invitation to open the microphone has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`, duration: MESSAGE_DURATION.NORMAL, }); return; } const request = await roomEngine.instance?.openRemoteDeviceByAdmin({ userId: userInfo.userId, device: TUIMediaDevice.kMicrophone, timeout: 0, requestCallback: (callbackInfo: { requestCallbackType: TUIRequestCallbackType; code: TUIErrorCode; }) => { roomStore.setRequestUserOpenMic({ userId: userInfo.userId, isRequesting: false, }); const { requestCallbackType, code } = callbackInfo; switch (requestCallbackType) { case TUIRequestCallbackType.kRequestError: if (code === TUIErrorCode.ERR_REQUEST_ID_REPEAT) { TUIToast({ type: TOAST_TYPE.WARNING, message: t( 'This member has already received the same request, please try again later' ), duration: MESSAGE_DURATION.NORMAL, }); } break; } }, }); TUIToast({ type: TOAST_TYPE.INFO, message: `${t('An invitation to open the microphone has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`, duration: MESSAGE_DURATION.NORMAL, }); if (request && request.requestId) { roomStore.setRequestUserOpenMic({ userId: userInfo.userId, isRequesting: true, requestId: request.requestId, }); } } } /** * Banned painting/unbanned painting **/ async function muteUserVideo(userInfo: UserInfo) { if (userInfo.hasVideoStream) { await roomEngine.instance?.closeRemoteDeviceByAdmin({ userId: userInfo.userId, device: TUIMediaDevice.kCamera, }); } else { if (userInfo.isRequestingUserOpenCamera) { TUIToast({ type: TOAST_TYPE.INFO, message: `${t('An invitation to open the camera has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`, duration: MESSAGE_DURATION.NORMAL, }); return; } const request = await roomEngine.instance?.openRemoteDeviceByAdmin({ userId: userInfo.userId, device: TUIMediaDevice.kCamera, timeout: 0, requestCallback: (callbackInfo: { requestCallbackType: TUIRequestCallbackType; code: TUIErrorCode; }) => { roomStore.setRequestUserOpenCamera({ userId: userInfo.userId, isRequesting: false, }); const { requestCallbackType, code } = callbackInfo; switch (requestCallbackType) { case TUIRequestCallbackType.kRequestError: if (code === TUIErrorCode.ERR_REQUEST_ID_REPEAT) { TUIToast({ type: TOAST_TYPE.WARNING, message: t( 'This member has already received the same request, please try again later' ), duration: MESSAGE_DURATION.NORMAL, }); } break; } }, }); TUIToast({ type: TOAST_TYPE.INFO, message: `${t('An invitation to open the camera has been sent to sb.', { name: roomService.getDisplayName(userInfo) })}`, duration: MESSAGE_DURATION.NORMAL, }); if (request && request.requestId) { roomStore.setRequestUserOpenCamera({ userId: userInfo.userId, isRequesting: true, requestId: request.requestId, }); } } } /** * Allow text chat / Cancel text chat **/ async function disableUserChat(userInfo: UserInfo) { const { isMessageDisabled } = userInfo; try { await roomEngine.instance?.disableSendingMessageByAdmin({ userId: userInfo.userId, isDisable: !isMessageDisabled, }); roomStore.setMuteUserChat(userInfo.userId, !isMessageDisabled); } catch (error) { TUIToast({ type: TOAST_TYPE.ERROR, message: t('Failed to disable chat'), duration: MESSAGE_DURATION.NORMAL, }); } } /** * Kick the user out of the room **/ async function kickOffUser(userInfo: UserInfo) { await roomEngine.instance?.kickRemoteUserOutOfRoom({ userId: userInfo.userId, }); roomStore.removeUserInfo(userInfo.userId); } /** * Transfer host to user */ async function handleTransferOwner(userInfo: UserInfo) { const roomInfo = await roomEngine.instance?.fetchRoomInfo(); if (roomInfo?.roomOwner === roomStore.localUser.userId) { try { if ( roomStore.localUser.hasScreenStream && roomStore.isScreenShareDisableForAllUser ) { eventBus.emit('ScreenShare:stopScreenShare'); } await roomEngine.instance?.changeUserRole({ userId: userInfo.userId, userRole: TUIRole.kRoomOwner, }); roomStore.setMasterUserId(userInfo.userId); TUIToast({ type: TOAST_TYPE.SUCCESS, message: t('The room owner has been transferred to sb', { name: roomService.getDisplayName(userInfo), }), duration: MESSAGE_DURATION.NORMAL, }); } catch (error) { TUIToast({ type: TOAST_TYPE.ERROR, message: t('Make host failed, please try again.'), duration: MESSAGE_DURATION.NORMAL, }); } } } /** * Set/Remove administrator permissions */ async function handleSetOrRevokeAdmin(userInfo: UserInfo) { const newRole = userInfo.userRole === TUIRole.kGeneralUser ? TUIRole.kAdministrator : TUIRole.kGeneralUser; await roomEngine.instance?.changeUserRole({ userId: userInfo.userId, userRole: newRole, }); const updatedUserName = roomStore.getUserName(userInfo.userId); const tipMessage = newRole === TUIRole.kAdministrator ? `${t('sb has been set as administrator', { name: updatedUserName })}` : `${t('The administrator status of sb has been withdrawn', { name: updatedUserName })}`; TUIToast({ type: TOAST_TYPE.SUCCESS, message: tipMessage }); roomStore.setRemoteUserRole(userInfo.userId, newRole); if (newRole === TUIRole.kGeneralUser && userInfo.hasScreenStream) { await roomEngine.instance?.closeRemoteDeviceByAdmin({ userId: userInfo.userId, device: TUIMediaDevice.kScreen, }); } } function handleOpenDialog(action: string) { switch (action) { case 'kickUser': isDialogVisible.value = true; Object.assign(dialogData, { title: t('Note'), content: kickOffDialogContent.value, confirmText: t('Confirm'), actionType: action, showInput: false, isConfirmButtonDisable: false, }); break; case 'transferOwner': isDialogVisible.value = true; Object.assign(dialogData, { title: transferOwnerTitle.value, content: t( 'After transfer the room owner, you will become a general user' ), confirmText: t('Confirm transfer'), actionType: action, showInput: false, isConfirmButtonDisable: false, }); break; case 'changeUserNameCard': tempUserName.value = props.userInfo.nameCard || props.userInfo.userName; if (isMobile) { isShowInput.value = true; document?.body?.appendChild(editorInputEleContainer.value); } else { isDialogVisible.value = true; } Object.assign(dialogData, { title: t('change name'), content: '', confirmText: t('Confirm'), actionType: action, showInput: true, isConfirmButtonDisable: computed(() => tempUserName.value === ''), }); break; } } const nameCardCheck = () => { const result = calculateByteLength(tempUserName.value) <= 32; !result && TUIToast({ type: TOAST_TYPE.WARNING, message: t('The user name cannot exceed 32 characters'), duration: MESSAGE_DURATION.NORMAL, }); return result; }; /** * change UserNameCard */ async function handleChangeUserNameCard(userInfo: UserInfo) { if (!nameCardCheck()) return; try { await roomEngine.instance?.changeUserNameCard({ userId: userInfo.userId, nameCard: tempUserName.value, }); TUIToast({ type: TOAST_TYPE.SUCCESS, message: t('Name changed successfully'), duration: MESSAGE_DURATION.NORMAL, }); } catch (error) { TUIToast({ type: TOAST_TYPE.ERROR, message: t('change name failed, please try again.'), duration: MESSAGE_DURATION.NORMAL, }); } } function handleAction(userInfo: UserInfo) { switch (dialogData.actionType) { case 'kickUser': kickOffUser(userInfo); break; case 'transferOwner': handleTransferOwner(userInfo); break; case 'changeUserNameCard': handleChangeUserNameCard(userInfo); isShowInput.value = false; } isDialogVisible.value = false; } function handleCancelDialog() { tempUserName.value = props.userInfo.nameCard || props.userInfo.userName; isDialogVisible.value = false; } return { props, isMe, isGeneralUser, controlList, kickOffDialogContent, handleCancelDialog, handleAction, isDialogVisible, dialogData, tempUserName, isShowInput, editorInputEle, editorInputEleContainer, }; }