diff --git a/client/web/src/plugin/component/index.tsx b/client/web/src/plugin/component/index.tsx index 8efdef4fbec..b9975ef84a1 100644 --- a/client/web/src/plugin/component/index.tsx +++ b/client/web/src/plugin/component/index.tsx @@ -65,6 +65,7 @@ export { ErrorBoundary } from '@/components/ErrorBoundary'; export { ErrorView } from '@/components/ErrorView'; export { UserAvatar } from '@/components/UserAvatar'; export { UserName } from '@/components/UserName'; +export { UserListItem } from '@/components/UserListItem'; export { Markdown, MarkdownEditor } from '@/components/Markdown'; export { Webview, WebviewKeepAlive } from '@/components/Webview'; export { Card } from '@/components/Card'; diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/Chat.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/Chat.tsx index 948005a4b8d..084c9ee75d0 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/Chat.tsx +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/Chat.tsx @@ -12,11 +12,6 @@ import * as React from 'react'; import { Translate } from '../../translate'; import { cloneSingleChild } from '../../utils/common'; import { useObservableState } from '../../utils/useObservableState'; -// import { useRoomContext } from '../context'; -// import { useObservableState } from '../hooks/internal/useObservableState'; -// import { cloneSingleChild } from '../utils'; -// import type { MessageFormatter } from '../components/ChatEntry'; -// import { ChatEntry } from '../components/ChatEntry'; export type { ChatMessage, ReceivedChatMessage }; diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/ControlBar.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/ControlBar.tsx index 2b5fa1c09f6..e18393c480c 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/ControlBar.tsx +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/ControlBar.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import { supportsScreenSharing } from '@livekit/components-core'; import { - ChatToggle, DisconnectButton, MediaDeviceMenu, StartAudio, @@ -13,15 +12,15 @@ import { } from '@livekit/components-react'; import { Translate } from '../../translate'; import { useMediaQuery } from '../../utils/useMediaQuery'; -import { ChatIcon } from './icons/ChatIcon'; -import { LeaveIcon } from './icons/LeaveIcon'; import { useMeetingContextState } from '../../context/MeetingContext'; +import { Icon } from '@capital/component'; /** @public */ export type ControlBarControls = { microphone?: boolean; camera?: boolean; chat?: boolean; + member?: boolean; screenShare?: boolean; leave?: boolean; }; @@ -71,13 +70,15 @@ export function ControlBar({ variation, controls, ...props }: ControlBarProps) { if (!localPermissions) { visibleControls.camera = false; visibleControls.chat = false; + visibleControls.member = false; visibleControls.microphone = false; visibleControls.screenShare = false; } else { visibleControls.camera ??= localPermissions.canPublish; visibleControls.microphone ??= localPermissions.canPublish; visibleControls.screenShare ??= localPermissions.canPublish; - visibleControls.chat ??= localPermissions.canPublishData && controls?.chat; + visibleControls.chat ??= localPermissions.canPublishData; + visibleControls.member ??= localPermissions.canSubscribe; } const showIcon = React.useMemo( @@ -137,14 +138,21 @@ export function ControlBar({ variation, controls, ...props }: ControlBarProps) { {visibleControls.chat && ( )} + {visibleControls.member && ( + + )} + {visibleControls.leave && ( - {showIcon && } + {showIcon && } {showText && Translate.leave} )} diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/Member.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/Member.tsx new file mode 100644 index 00000000000..699aef0df72 --- /dev/null +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/Member.tsx @@ -0,0 +1,54 @@ +import { useParticipants } from '@livekit/components-react'; +import * as React from 'react'; +import styled from 'styled-components'; +import { Icon, UserListItem } from '@capital/component'; +import { useEvent } from '@capital/common'; +import type { Participant } from 'livekit-client'; +import { Translate } from '../../translate'; + +const MemberList = styled.div` + display: flex; + flex-direction: column; + align-items: stretch; + width: clamp(200px, 55ch, 60ch); + background-color: var(--lk-bg2); + border-left: 1px solid var(--lk-border-color); + padding: 8px; +`; + +const IsSpeakingTip = styled.div` + font-size: 12px; + opacity: 0.6; +`; + +export const Member: React.FC = React.memo(() => { + const participants = useParticipants(); + + const getAction = useEvent((participant: Participant) => { + return [ + !participant.isSpeaking && ( + ({Translate.isSpeaking}) + ), +
+ {participant.isMicrophoneEnabled ? ( + + ) : ( + + )} +
, + ]; + }); + + return ( + + {participants.map((member) => ( + + ))} + + ); +}); +Member.displayName = 'Member'; diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/VideoConference.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/VideoConference.tsx index 9c603efcc31..87d77540f86 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/VideoConference.tsx +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/VideoConference.tsx @@ -29,6 +29,7 @@ import { ControlBar } from './ControlBar'; import { Chat } from './Chat'; import { FocusLayout } from './FocusLayout'; import { useMeetingContextState } from '../../context/MeetingContext'; +import { Member } from './Member'; /** * @public @@ -145,12 +146,14 @@ export const VideoConference: React.FC = React.memo( )} - + {rightPanel === 'chat' && ( )} + + {rightPanel === 'member' && } )} diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/icons/ChatIcon.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/icons/ChatIcon.tsx deleted file mode 100644 index 957f3c24dc8..00000000000 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/icons/ChatIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; -import type { SVGProps } from 'react'; - -export const ChatIcon = (props: SVGProps) => ( - - - - -); diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/icons/LeaveIcon.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/icons/LeaveIcon.tsx deleted file mode 100644 index eed41533070..00000000000 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/icons/LeaveIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; -import type { SVGProps } from 'react'; - -export const LeaveIcon = (props: SVGProps) => ( - - - - -); diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts index 996705f8331..c082d4b9d3f 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts @@ -41,6 +41,10 @@ export const Translate = { 'zh-CN': '聊天', 'en-US': 'Chat', }), + member: localTrans({ + 'zh-CN': '成员', + 'en-US': 'Member', + }), leave: localTrans({ 'zh-CN': '离开', 'en-US': 'Leave', @@ -53,6 +57,10 @@ export const Translate = { 'zh-CN': '输入消息...', 'en-US': 'Enter a message...', }), + isSpeaking: localTrans({ + 'zh-CN': '正在发言', + 'en-US': 'Is speaking', + }), nobodyInMeeting: localTrans({ 'zh-CN': '当前无人在会...', 'en-US': 'Nobody in Meeting...', diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/utils/useObservableState.ts b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/utils/useObservableState.ts index e174f5503df..9d15592eeb0 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/utils/useObservableState.ts +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/utils/useObservableState.ts @@ -10,11 +10,13 @@ export function useObservableState( startWith: T ) { const [state, setState] = React.useState(startWith); + React.useEffect(() => { // observable state doesn't run in SSR if (typeof window === 'undefined' || !observable) return; const subscription = observable.subscribe(setState); return () => subscription.unsubscribe(); }, [observable]); + return state; }