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 _head from 'lodash/head';
import _upperCase from 'lodash/upperCase';
@ -17,7 +17,8 @@ export interface AvatarProps extends AntdAvatarProps {
name?: string;
isOnline?: boolean;
}
export const Avatar: React.FC<AvatarProps> = React.memo((_props) => {
const _Avatar: React.FC<AvatarProps> = React.memo((_props) => {
const { isOnline, ...props } = _props;
const src = isValidStr(props.src) ? imageUrlParser(props.src) : undefined;
@ -85,4 +86,11 @@ export const Avatar: React.FC<AvatarProps> = React.memo((_props) => {
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 { Avatar } from 'tailchat-design';
import { useCachedUserInfo } from 'tailchat-shared';
interface UserAvatarProps {
interface UserAvatarProps extends AvatarProps {
userId: string;
className?: string;
style?: React.CSSProperties;
size?: 'large' | 'small' | 'default' | number;
}
/**
*
*/
export const UserAvatar: React.FC<UserAvatarProps> = React.memo((props) => {
const cachedUserInfo = useCachedUserInfo(props.userId);
const { userId, ...avatarProps } = props;
const cachedUserInfo = useCachedUserInfo(userId);
return (
<Avatar
className={props.className}
style={props.style}
size={props.size}
{...avatarProps}
src={cachedUserInfo.avatar}
name={cachedUserInfo.nickname}
/>

@ -1,7 +1,7 @@
import type { TcContext } from 'tailchat-server-sdk';
import { TcService, TcDbService } from 'tailchat-server-sdk';
import type { LivekitDocument, LivekitModel } from '../models/livekit';
import { AccessToken } from 'livekit-server-sdk';
import { AccessToken, RoomServiceClient } from 'livekit-server-sdk';
/**
* livekit
@ -12,6 +12,8 @@ interface LivekitService
extends TcService,
TcDbService<LivekitDocument, LivekitModel> {}
class LivekitService extends TcService {
// roomServiceClient: RoomServiceClient = null;
get serviceName() {
return 'plugin:com.msgbyte.livekit';
}
@ -39,6 +41,10 @@ class LivekitService extends TcService {
return false;
}
getRoomServiceClient() {
return new RoomServiceClient(this.livekitUrl, this.apiKey, this.apiSecret);
}
onInit() {
this.registerAvailableAction(() => this.serverAvailable);
@ -52,7 +58,16 @@ class LivekitService extends TcService {
this.registerLocalDb(require('../models/livekit').default);
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) {
@ -90,6 +105,21 @@ class LivekitService extends TcService {
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;

@ -45,6 +45,7 @@ const _LivekitView: React.FC<LivekitViewProps> = React.memo((props) => {
) : (
<div style={{ display: 'grid', placeItems: 'center', height: '100%' }}>
<PreJoinView
roomName={props.roomName}
onError={handleError}
defaults={{
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 { log } from '@livekit/components-core';
import { Translate } from '../../translate';
import { ParticipantAvatars } from '../ParticipantAvatars';
/**
* Fork <PreJoin /> from "@livekit/components-react"
@ -43,6 +44,7 @@ export type PreJoinProps = Omit<
React.HTMLAttributes<HTMLDivElement>,
'onSubmit'
> & {
roomName: string;
/** This function is called with the `LocalUserChoices` if validation is passed. */
onSubmit?: (values: LocalUserChoices) => void;
/**
@ -77,6 +79,7 @@ export type PreJoinProps = Omit<
*/
export const PreJoinView: React.FC<PreJoinProps> = React.memo(
({
roomName,
defaults = {},
onValidate,
onSubmit,
@ -191,6 +194,8 @@ export const PreJoinView: React.FC<PreJoinProps> = React.memo(
return (
<div className="lk-prejoin" {...htmlProps}>
<ParticipantAvatars roomName={roomName} />
<div className="lk-video-container" style={{ borderRadius: 10 }}>
{videoTrack && (
<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': '输入消息...',
'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