@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
text/typescript
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,
};
}