feat: add roomMembers list in prejoin view

pull/105/merge
moonrailgun 2 years ago
parent fe3b68fb75
commit d68f75b491

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { ForwardRefExoticComponent, useMemo } from 'react';
import { Avatar as AntdAvatar, Badge } from 'antd'; import { Avatar as AntdAvatar, Badge } from 'antd';
import _head from 'lodash/head'; import _head from 'lodash/head';
import _upperCase from 'lodash/upperCase'; import _upperCase from 'lodash/upperCase';
@ -17,7 +17,8 @@ export interface AvatarProps extends AntdAvatarProps {
name?: string; name?: string;
isOnline?: boolean; isOnline?: boolean;
} }
export const Avatar: React.FC<AvatarProps> = React.memo((_props) => {
const _Avatar: React.FC<AvatarProps> = React.memo((_props) => {
const { isOnline, ...props } = _props; const { isOnline, ...props } = _props;
const src = isValidStr(props.src) ? imageUrlParser(props.src) : undefined; const src = isValidStr(props.src) ? imageUrlParser(props.src) : undefined;
@ -85,4 +86,11 @@ export const Avatar: React.FC<AvatarProps> = React.memo((_props) => {
return inner; return inner;
}); });
Avatar.displayName = 'Avatar'; _Avatar.displayName = 'Avatar';
type CompoundedComponent = ForwardRefExoticComponent<AvatarProps> & {
Group: typeof AntdAvatar.Group;
};
export const Avatar: CompoundedComponent = _Avatar as any;
Avatar.Group = AntdAvatar.Group;

@ -1,25 +1,22 @@
import type { AvatarProps } from 'antd';
import React from 'react'; import React from 'react';
import { Avatar } from 'tailchat-design'; import { Avatar } from 'tailchat-design';
import { useCachedUserInfo } from 'tailchat-shared'; import { useCachedUserInfo } from 'tailchat-shared';
interface UserAvatarProps { interface UserAvatarProps extends AvatarProps {
userId: string; userId: string;
className?: string;
style?: React.CSSProperties;
size?: 'large' | 'small' | 'default' | number;
} }
/** /**
* *
*/ */
export const UserAvatar: React.FC<UserAvatarProps> = React.memo((props) => { export const UserAvatar: React.FC<UserAvatarProps> = React.memo((props) => {
const cachedUserInfo = useCachedUserInfo(props.userId); const { userId, ...avatarProps } = props;
const cachedUserInfo = useCachedUserInfo(userId);
return ( return (
<Avatar <Avatar
className={props.className} {...avatarProps}
style={props.style}
size={props.size}
src={cachedUserInfo.avatar} src={cachedUserInfo.avatar}
name={cachedUserInfo.nickname} name={cachedUserInfo.nickname}
/> />

@ -1,7 +1,7 @@
import type { TcContext } from 'tailchat-server-sdk'; import type { TcContext } from 'tailchat-server-sdk';
import { TcService, TcDbService } from 'tailchat-server-sdk'; import { TcService, TcDbService } from 'tailchat-server-sdk';
import type { LivekitDocument, LivekitModel } from '../models/livekit'; import type { LivekitDocument, LivekitModel } from '../models/livekit';
import { AccessToken } from 'livekit-server-sdk'; import { AccessToken, RoomServiceClient } from 'livekit-server-sdk';
/** /**
* livekit * livekit
@ -12,6 +12,8 @@ interface LivekitService
extends TcService, extends TcService,
TcDbService<LivekitDocument, LivekitModel> {} TcDbService<LivekitDocument, LivekitModel> {}
class LivekitService extends TcService { class LivekitService extends TcService {
// roomServiceClient: RoomServiceClient = null;
get serviceName() { get serviceName() {
return 'plugin:com.msgbyte.livekit'; return 'plugin:com.msgbyte.livekit';
} }
@ -39,6 +41,10 @@ class LivekitService extends TcService {
return false; return false;
} }
getRoomServiceClient() {
return new RoomServiceClient(this.livekitUrl, this.apiKey, this.apiSecret);
}
onInit() { onInit() {
this.registerAvailableAction(() => this.serverAvailable); this.registerAvailableAction(() => this.serverAvailable);
@ -52,7 +58,16 @@ class LivekitService extends TcService {
this.registerLocalDb(require('../models/livekit').default); this.registerLocalDb(require('../models/livekit').default);
this.registerAction('url', this.url); this.registerAction('url', this.url);
this.registerAction('generateToken', this.generateToken); this.registerAction('generateToken', this.generateToken, {
params: {
roomName: 'string',
},
});
this.registerAction('roomMembers', this.roomMembers, {
params: {
roomName: 'string',
},
});
} }
async url(ctx: TcContext) { async url(ctx: TcContext) {
@ -90,6 +105,21 @@ class LivekitService extends TcService {
accessToken, accessToken,
}; };
} }
async roomMembers(ctx: TcContext<{ roomName: string }>) {
if (!this.serverAvailable) {
throw new Error('livekit server not available');
}
try {
const client = this.getRoomServiceClient();
const participants = await client.listParticipants(ctx.params.roomName);
return participants;
} catch (err) {
return [];
}
}
} }
export default LivekitService; export default LivekitService;

@ -45,6 +45,7 @@ const _LivekitView: React.FC<LivekitViewProps> = React.memo((props) => {
) : ( ) : (
<div style={{ display: 'grid', placeItems: 'center', height: '100%' }}> <div style={{ display: 'grid', placeItems: 'center', height: '100%' }}>
<PreJoinView <PreJoinView
roomName={props.roomName}
onError={handleError} onError={handleError}
defaults={{ defaults={{
videoEnabled: false, videoEnabled: false,

@ -0,0 +1,84 @@
import React, { useEffect, useRef } from 'react';
import { Avatar, Tooltip, UserAvatar, UserName } from '@capital/component';
import { useAsyncFn, useEvent } from '@capital/common';
import { request } from '../request';
import { Translate } from '../translate';
interface Props {
roomName: string;
}
export const ParticipantAvatars: React.FC<Props> = React.memo((props) => {
const containerEl = useRef<HTMLDivElement>(null);
const [{ value: participants = [] }, _handleFetchParticipants] =
useAsyncFn(async () => {
const { data } = await request.post('roomMembers', {
roomName: props.roomName,
});
return data ?? [];
}, [props.roomName]);
const handleFetchParticipants = useEvent(_handleFetchParticipants);
useEffect(() => {
let timer: number;
const fn = async () => {
if (containerEl.current && containerEl.current.offsetWidth !== 0) {
// 该元素可见
await handleFetchParticipants();
}
timer = window.setTimeout(fn, 3000);
};
timer = window.setTimeout(fn, 3000);
return () => {
if (timer) {
clearTimeout(timer);
}
};
}, []);
let inner: React.ReactNode;
if (participants.length === 0) {
inner = Translate.nobodyInMeeting;
} else {
inner = (
<>
<div>{Translate.peopleInMeeting}</div>
<Avatar.Group
maxCount={4}
maxPopoverTrigger="click"
maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}
>
{[
...participants,
...participants,
...participants,
...participants,
...participants,
...participants,
].map((info, i) => (
<Tooltip
key={`${info.sid}#${i}`}
title={<UserName userId={info.identity} />}
placement="top"
>
<UserAvatar userId={info.identity} />
</Tooltip>
))}
</Avatar.Group>
</>
);
}
return (
<div ref={containerEl} style={{ textAlign: 'center' }}>
{inner}
</div>
);
});
ParticipantAvatars.displayName = 'ParticipantAvatars';

@ -16,6 +16,7 @@ import * as React from 'react';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { log } from '@livekit/components-core'; import { log } from '@livekit/components-core';
import { Translate } from '../../translate'; import { Translate } from '../../translate';
import { ParticipantAvatars } from '../ParticipantAvatars';
/** /**
* Fork <PreJoin /> from "@livekit/components-react" * Fork <PreJoin /> from "@livekit/components-react"
@ -43,6 +44,7 @@ export type PreJoinProps = Omit<
React.HTMLAttributes<HTMLDivElement>, React.HTMLAttributes<HTMLDivElement>,
'onSubmit' 'onSubmit'
> & { > & {
roomName: string;
/** This function is called with the `LocalUserChoices` if validation is passed. */ /** This function is called with the `LocalUserChoices` if validation is passed. */
onSubmit?: (values: LocalUserChoices) => void; onSubmit?: (values: LocalUserChoices) => void;
/** /**
@ -77,6 +79,7 @@ export type PreJoinProps = Omit<
*/ */
export const PreJoinView: React.FC<PreJoinProps> = React.memo( export const PreJoinView: React.FC<PreJoinProps> = React.memo(
({ ({
roomName,
defaults = {}, defaults = {},
onValidate, onValidate,
onSubmit, onSubmit,
@ -191,6 +194,8 @@ export const PreJoinView: React.FC<PreJoinProps> = React.memo(
return ( return (
<div className="lk-prejoin" {...htmlProps}> <div className="lk-prejoin" {...htmlProps}>
<ParticipantAvatars roomName={roomName} />
<div className="lk-video-container" style={{ borderRadius: 10 }}> <div className="lk-video-container" style={{ borderRadius: 10 }}>
{videoTrack && ( {videoTrack && (
<video <video

@ -0,0 +1,3 @@
import { createPluginRequest } from '@capital/common';
export const request = createPluginRequest('com.msgbyte.livekit');

@ -53,4 +53,12 @@ export const Translate = {
'zh-CN': '输入消息...', 'zh-CN': '输入消息...',
'en-US': 'Enter a message...', 'en-US': 'Enter a message...',
}), }),
nobodyInMeeting: localTrans({
'zh-CN': '当前无人在会...',
'en-US': 'Nobody in Meeting...',
}),
peopleInMeeting: localTrans({
'zh-CN': '这些人正在会中:',
'en-US': 'Here is people in meeting:',
}),
}; };

Loading…
Cancel
Save