feat: add group badge for current room participants

pull/105/merge
moonrailgun 2 years ago
parent 80347f9c41
commit d99e556c79

@ -34,11 +34,11 @@ export type GroupPanelFeature =
export interface GroupPanel { export interface GroupPanel {
/** /**
* * , objectId
*/ */
id: string; id: string;
/** /**
* * : `com.msgbyte.xxx/panel`
*/ */
name: string; name: string;
parentId?: string; parentId?: string;

@ -7,7 +7,6 @@ import {
regSocketEventListener, regSocketEventListener,
PermissionItemType, PermissionItemType,
GroupPanelFeature, GroupPanelFeature,
InboxItem,
buildRegMap, buildRegMap,
BasicInboxItem, BasicInboxItem,
} from 'tailchat-shared'; } from 'tailchat-shared';
@ -255,7 +254,8 @@ export const [pluginPermission, regPluginPermission] =
*/ */
export const [pluginGroupPanelBadges, regGroupPanelBadge] = buildRegList<{ export const [pluginGroupPanelBadges, regGroupPanelBadge] = buildRegList<{
name: string; name: string;
render: (groupId: string, panelId: string) => React.ReactNode; panelType: string;
render: React.ComponentType<{ groupId: string; panelId: string }>;
}>(); }>();
/** /**

@ -21,7 +21,11 @@ export const GroupAckPanelItem: React.FC<GroupTextPanelItemProps> = React.memo(
const { groupId, panel } = props; const { groupId, panel } = props;
const panelId = panel.id; const panelId = panel.id;
const hasUnread = useGroupTextPanelUnread(panelId); const hasUnread = useGroupTextPanelUnread(panelId);
const extraBadge = useGroupPanelExtraBadge(groupId, panelId); const extraBadge = useGroupPanelExtraBadge(
groupId,
panelId,
panel.pluginPanelName ?? ''
);
const { checkIsMuted } = useUserNotifyMute(); const { checkIsMuted } = useUserNotifyMute();
const isMuted = checkIsMuted(panelId, groupId); const isMuted = checkIsMuted(panelId, groupId);

@ -38,7 +38,11 @@ export const SidebarItem: React.FC<{
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { markConverseAllAck } = useConverseAck(panelId); const { markConverseAllAck } = useConverseAck(panelId);
const extraMenuItems = useExtraMenuItems(panel); const extraMenuItems = useExtraMenuItems(panel);
const extraBadge = useGroupPanelExtraBadge(groupId, panelId); const extraBadge = useGroupPanelExtraBadge(
groupId,
panelId,
panel.pluginPanelName ?? ''
);
const { checkIsMuted, toggleMute } = useUserNotifyMute(); const { checkIsMuted, toggleMute } = useUserNotifyMute();
if (!groupInfo) { if (!groupInfo) {

@ -66,7 +66,8 @@ export function useExtraMenuItems(panel: GroupPanel): ItemType[] {
*/ */
export function useGroupPanelExtraBadge( export function useGroupPanelExtraBadge(
groupId: string, groupId: string,
panelId: string panelId: string,
panelType: string
): React.ReactNode[] { ): React.ReactNode[] {
const update = useUpdate(); const update = useUpdate();
@ -74,11 +75,17 @@ export function useGroupPanelExtraBadge(
update(); update();
}); });
const extraBadge = pluginGroupPanelBadges.map((item) => ( const extraBadge = pluginGroupPanelBadges
<React.Fragment key={panelId + item.name}> .filter((item) => item.panelType === panelType)
{item.render(groupId, panelId)} .map((item) => {
</React.Fragment> const Component = item.render;
));
return (
<React.Fragment key={panelId + item.name}>
<Component groupId={groupId} panelId={panelId} />
</React.Fragment>
);
});
return extraBadge; return extraBadge;
} }

@ -9,7 +9,7 @@ export function isGroupAckPanel(panel: GroupPanel) {
return true; return true;
} }
if (panel.type === GroupPanelType.GROUP) { if (panel.type === GroupPanelType.PLUGIN) {
const pluginPanelInfo = findPluginPanelInfoByName(panel.name); const pluginPanelInfo = findPluginPanelInfoByName(panel.name);
return pluginPanelInfo?.feature?.includes('ack') ?? false; return pluginPanelInfo?.feature?.includes('ack') ?? false;
} }

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

@ -0,0 +1,37 @@
import { Avatar, Tooltip, UserAvatar, UserName } from '@capital/component';
import React, { useEffect } from 'react';
import { useRoomParticipants } from '../utils/useRoomParticipants';
export const LivekitPanelBadge: React.FC<{
groupId: string;
panelId: string;
}> = React.memo((props) => {
const roomName = `${props.groupId}#${props.panelId}`;
const { participants, fetchParticipants } = useRoomParticipants(roomName);
useEffect(() => {
fetchParticipants();
}, []);
return (
<Avatar.Group
maxCount={4}
maxPopoverTrigger="click"
style={{ verticalAlign: 'middle' }}
maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}
>
{participants.map((info, i) => (
<Tooltip
key={`${info.sid}#${i}`}
title={<UserName userId={info.identity} />}
placement="top"
>
<UserAvatar userId={info.identity} size={24} />
</Tooltip>
))}
</Avatar.Group>
);
});
LivekitPanelBadge.displayName = 'LivekitPanelBadge';
export default LivekitPanelBadge;

@ -1,4 +1,8 @@
import { regCustomPanel, regGroupPanel } from '@capital/common'; import {
regCustomPanel,
regGroupPanel,
regGroupPanelBadge,
} from '@capital/common';
import { Loadable } from '@capital/component'; import { Loadable } from '@capital/component';
import { useIconIsShow } from './navbar/useIconIsShow'; import { useIconIsShow } from './navbar/useIconIsShow';
import { Translate } from './translate'; import { Translate } from './translate';
@ -16,6 +20,15 @@ regGroupPanel({
}), }),
}); });
regGroupPanelBadge({
name: `${PLUGIN_ID}/livekitPanelBadge`,
panelType: `${PLUGIN_ID}/livekitPanel`,
render: Loadable(() => import('./group/LivekitPanelBadge'), {
componentName: `${PLUGIN_ID}:LivekitPanelBadge`,
fallback: null,
}),
});
regCustomPanel({ regCustomPanel({
position: 'navbar-more', position: 'navbar-more',
icon: 'mingcute:voice-line', icon: 'mingcute:voice-line',

@ -0,0 +1,34 @@
import { useAsyncFn, useEvent } from '@capital/common';
import { useMemo, useRef } from 'react';
import { request } from '../request';
export function useRoomParticipants(roomName: string) {
const [{ value: participants = [], loading }, _handleFetchParticipants] =
useAsyncFn(async () => {
const { data } = await request.post('roomMembers', {
roomName,
});
return data ?? [];
}, [roomName]);
const fetchParticipants = useEvent(_handleFetchParticipants);
const lockRef = useRef(false);
const isFirstLoading = useMemo(() => {
if (loading && lockRef.current === false) {
lockRef.current = true;
return true;
}
return false;
}, [loading]);
return {
loading,
isFirstLoading,
participants,
fetchParticipants,
};
}
Loading…
Cancel
Save