All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s
Integrate profile effects, nameplates, and avatar decorations with live catalog browsing, Matrix profile storage, and rendering across user heroes, DMs, messages, and the sidebar avatar.
217 lines
8.2 KiB
TypeScript
217 lines
8.2 KiB
TypeScript
import { Box, Button, color, config, Text, toRem } from 'folds';
|
|
import { Icon, Icons } from '../icons';
|
|
import React from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { UserHero } from './UserHero';
|
|
import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
|
|
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
|
import { usePowerLevels } from '../../hooks/usePowerLevels';
|
|
import { useRoom } from '../../hooks/useRoom';
|
|
import { useUserPresence } from '../../hooks/useUserPresence';
|
|
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
|
|
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
|
import { PowerChip } from './PowerChip';
|
|
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
|
|
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
|
|
import { useMembership } from '../../hooks/useMembership';
|
|
import { Membership } from '../../../types/matrix/room';
|
|
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
|
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
|
import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
|
|
import { CreatorChip } from './CreatorChip';
|
|
import { getDirectCreatePath, withSearchParam } from '../../pages/pathUtils';
|
|
import { DirectCreateSearchParams } from '../../pages/paths';
|
|
import { getTextShadowColor } from '../../utils/common';
|
|
import classNames from 'classnames';
|
|
import { BreakWord, LineClamp3 } from '../../styles/Text.css';
|
|
import colorMXID, { getColorMXIDValue } from '../../../util/colorMXID';
|
|
import { useOtherUserColor } from '../../hooks/useUserColor';
|
|
|
|
type UserRoomProfileProps = {
|
|
userId: string;
|
|
};
|
|
export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
|
const mx = useMatrixClient();
|
|
const useAuthentication = useMediaAuthentication();
|
|
const navigate = useNavigate();
|
|
const closeUserRoomProfile = useCloseUserRoomProfile();
|
|
const ignoredUsers = useIgnoredUsers();
|
|
const ignored = ignoredUsers.includes(userId);
|
|
|
|
const room = useRoom();
|
|
const powerLevels = usePowerLevels(room);
|
|
const creators = useRoomCreators(room);
|
|
|
|
const permissions = useRoomPermissions(creators, powerLevels);
|
|
const { hasMorePower } = useMemberPowerCompare(creators, powerLevels);
|
|
|
|
const myUserId = mx.getSafeUserId();
|
|
const creator = creators.has(userId);
|
|
|
|
const canKickUser = permissions.action('kick', myUserId) && hasMorePower(myUserId, userId);
|
|
const canBanUser = permissions.action('ban', myUserId) && hasMorePower(myUserId, userId);
|
|
const canUnban = permissions.action('ban', myUserId);
|
|
const canInvite = permissions.action('invite', myUserId);
|
|
|
|
const member = room.getMember(userId);
|
|
const membership = useMembership(room, userId);
|
|
|
|
const server = getMxIdServer(userId);
|
|
const displayName = getMemberDisplayName(room, userId);
|
|
const avatarMxc = getMemberAvatarMxc(room, userId);
|
|
const avatarUrl = (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined;
|
|
|
|
const presence = useUserPresence(userId);
|
|
const userColor = useOtherUserColor(userId);
|
|
|
|
const handleMessage = () => {
|
|
closeUserRoomProfile();
|
|
const directSearchParam: DirectCreateSearchParams = {
|
|
userId,
|
|
};
|
|
navigate(withSearchParam(getDirectCreatePath(), directSearchParam));
|
|
};
|
|
|
|
const hasBottomContent =
|
|
ignored ||
|
|
(!!member && membership === Membership.Ban) ||
|
|
(!!member && membership === Membership.Leave && !!member.events.member && member.events.member.getSender() !== userId) ||
|
|
(!!member && membership === Membership.Invite) ||
|
|
(canInvite && membership === Membership.Leave) ||
|
|
(canKickUser && membership === Membership.Join) ||
|
|
(canBanUser && membership !== Membership.Ban);
|
|
|
|
// Use userColor for text, or fall back to colorMXID
|
|
const profileColor = userColor ? userColor : getColorMXIDValue(userId);
|
|
const profileTextShadow = `0 1px 4px ${getTextShadowColor(profileColor)}`;
|
|
|
|
return (
|
|
<Box direction="Column">
|
|
<UserHero
|
|
userId={userId}
|
|
avatarUrl={avatarUrl}
|
|
avatarMxc={avatarMxc}
|
|
presence={presence && presence.lastActiveTs !== 0 ? presence : undefined}
|
|
>
|
|
<Box
|
|
direction="Column"
|
|
gap="200"
|
|
alignItems="Center"
|
|
style={{
|
|
padding: config.space.S400,
|
|
paddingTop: `calc(${config.space.S200} + ${toRem(36)})`,
|
|
marginTop: toRem(-36),
|
|
}}
|
|
>
|
|
<Text
|
|
size="H4"
|
|
className={classNames(BreakWord, LineClamp3)}
|
|
style={{
|
|
color: getMemberDisplayName(room, userId) !== userId ? profileColor : undefined,
|
|
textShadow: profileTextShadow,
|
|
}}
|
|
>
|
|
{getMemberDisplayName(room, userId)}
|
|
</Text>
|
|
|
|
<Text
|
|
size="T200"
|
|
className={BreakWord}
|
|
style={{ color: profileColor, textShadow: profileTextShadow }}
|
|
>
|
|
{userId}
|
|
</Text>
|
|
|
|
{presence?.status && (
|
|
<Box
|
|
style={{
|
|
backgroundColor: color.Surface.Container,
|
|
border: `${toRem(1)} solid ${color.Surface.ContainerLine}`,
|
|
padding: `${toRem(6)} ${toRem(10)}`,
|
|
borderRadius: toRem(16),
|
|
maxWidth: toRem(250),
|
|
}}
|
|
>
|
|
<Text size="T300" className={BreakWord}>
|
|
{presence.status}
|
|
</Text>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</UserHero>
|
|
|
|
<Box direction="Column" gap="200" alignItems="Center" style={{ padding: config.space.S400, textAlign: 'center' }}>
|
|
{/* Chips Row */}
|
|
<Box alignItems="Center" gap="200" wrap="Wrap" justifyContent="Center">
|
|
{server && <ServerChip server={server} />}
|
|
<ShareChip userId={userId} />
|
|
{creator ? <CreatorChip /> : <PowerChip userId={userId} />}
|
|
</Box>
|
|
|
|
{/* Message Button */}
|
|
{userId !== myUserId && (
|
|
<Button
|
|
size="300"
|
|
variant="Primary"
|
|
fill="Solid"
|
|
radii="300"
|
|
before={<Icon size="50" src={Icons.Message} filled />}
|
|
onClick={handleMessage}
|
|
>
|
|
<Text size="B300">Message</Text>
|
|
</Button>
|
|
)}
|
|
|
|
{/* Additional Chips if needed */}
|
|
{userId !== myUserId && (
|
|
<Box alignItems="Center" gap="200" wrap="Wrap" justifyContent="Center">
|
|
<MutualRoomsChip userId={userId} />
|
|
<OptionsChip userId={userId} />
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
{hasBottomContent && (
|
|
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
|
|
{ignored && <IgnoredUserAlert />}
|
|
{member && membership === Membership.Ban && (
|
|
<UserBanAlert
|
|
userId={userId}
|
|
reason={member.events.member?.getContent().reason}
|
|
canUnban={canUnban}
|
|
bannedBy={member.events.member?.getSender()}
|
|
ts={member.events.member?.getTs()}
|
|
/>
|
|
)}
|
|
{member &&
|
|
membership === Membership.Leave &&
|
|
member.events.member &&
|
|
member.events.member.getSender() !== userId && (
|
|
<UserKickAlert
|
|
reason={member.events.member?.getContent().reason}
|
|
kickedBy={member.events.member?.getSender()}
|
|
ts={member.events.member?.getTs()}
|
|
/>
|
|
)}
|
|
{member && membership === Membership.Invite && (
|
|
<UserInviteAlert
|
|
userId={userId}
|
|
reason={member.events.member?.getContent().reason}
|
|
canKick={canKickUser}
|
|
invitedBy={member.events.member?.getSender()}
|
|
ts={member.events.member?.getTs()}
|
|
/>
|
|
)}
|
|
<UserModeration
|
|
userId={userId}
|
|
canInvite={canInvite && membership === Membership.Leave}
|
|
canKick={canKickUser && membership === Membership.Join}
|
|
canBan={canBanUser && membership !== Membership.Ban}
|
|
/>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|