// SPDX-License-Identifier: AGPL-3.0-or-later import {OutlineFrame} from '@app/features/app/components/layout/OutlineFrame'; import Authentication from '@app/features/auth/state/Authentication'; import styles from '@app/features/channel/components/ChannelMembers.module.css'; import {MemberListContainer} from '@app/features/channel/components/MemberListContainer'; import {MemberListItem} from '@app/features/channel/components/MemberListItem'; import {MemberListSkeleton, MemberListSkeletonVariant} from '@app/features/channel/components/MemberListSkeleton'; import type {Channel} from '@app/features/channel/models/Channel'; import Guilds from '@app/features/guild/state/Guilds'; import {OFFLINE_DESCRIPTOR, ONLINE_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; import {getCachedNumberFormat} from '@app/features/i18n/utils/IntlCache'; import GuildMembers from '@app/features/member/state/GuildMembers'; import LocalPresence from '@app/features/presence/state/LocalPresence'; import ThreadRoster, {type ThreadRosterMember} from '@app/features/threads/state/ThreadRoster'; import type {User} from '@app/features/user/models/User'; import type {CustomStatus} from '@app/features/user/state/CustomStatus'; import Users from '@app/features/user/state/Users'; import * as NicknameUtils from '@app/features/user/utils/NicknameUtils'; import {isOfflineStatus, type StatusType} from '@fluxer/constants/src/StatusConstants'; import {useLingui} from '@lingui/react/macro'; import {observer} from 'mobx-react-lite'; import {useEffect} from 'react'; export function useThreadRosterSubscription(thread: Channel | undefined): void { const guildId = thread?.guildId; const threadId = thread?.id; useEffect(() => { if (!guildId || !threadId) return; return ThreadRoster.subscribe(guildId, threadId); }, [guildId, threadId]); } interface RosterRow { user: User; status: StatusType; customStatus: CustomStatus | null; } interface RosterGroupProps { id: string; label: string; members: ReadonlyArray; thread: Channel; ownerId: string | null; } const RosterGroup = observer(({id, label, members, thread, ownerId}: RosterGroupProps) => { const {i18n} = useLingui(); if (members.length === 0) return null; return (
{label} {'—'} {getCachedNumberFormat(i18n.locale).format(members.length)}
{members.map(({user, status, customStatus}) => ( ))}
); }); function resolveMembers(guildId: string | undefined, roster: ReadonlyArray): Array { const members: Array = []; for (const entry of roster) { const user = Users.getUser(entry.userId); if (!user) continue; if (user.id === Authentication.currentUserId) { const status = LocalPresence.getStatus(); members.push({user, status, customStatus: isOfflineStatus(status) ? null : LocalPresence.customStatus}); } else { members.push({user, status: entry.status, customStatus: entry.customStatus}); } } members.sort((a, b) => NicknameUtils.getNickname(a.user, guildId).localeCompare(NicknameUtils.getNickname(b.user, guildId)), ); return members; } export const ThreadMembersList = observer(({thread}: {thread: Channel}) => { const {i18n} = useLingui(); useThreadRosterSubscription(thread); const roster = ThreadRoster.getMembers(thread.id); if (!roster) { return ( ); } const members = resolveMembers(thread.guildId, roster); const ownerId = thread.guildId ? (Guilds.getGuild(thread.guildId)?.ownerId ?? null) : null; return ( <> !isOfflineStatus(member.status))} thread={thread} ownerId={ownerId} data-flx="threads.thread-members-panel.thread-members-list.roster-group.online" /> isOfflineStatus(member.status))} thread={thread} ownerId={ownerId} data-flx="threads.thread-members-panel.thread-members-list.roster-group.offline" /> ); }); export const ThreadMembersPanel = observer(({thread}: {thread: Channel}) => ( ));