DP18: add useMemberAvatar hook and adopt it in reader avatars
Add a reactive `useMemberAvatar(room, userId, w?, h?, resize?)` hook
returning { name, avatarUrl }, standardizing the member name + avatar
trio and the RoomStateEvent.Members reactivity pattern (N6). Convert
ReadReceiptAvatars and EventReaders to render per-user avatars via a
small child component using the hook, preserving exact sizes, fallback
rendering, TDS pill/tooltip styling and behavior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,12 +15,10 @@ import {
|
||||
} from 'folds';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import { useRoomEventReaders } from '../../hooks/useRoomEventReaders';
|
||||
import { getMemberDisplayName } from '../../utils/room';
|
||||
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||
import * as css from './EventReaders.css';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { UserAvatar } from '../user-avatar';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useMemberAvatar } from '../../hooks/useMemberAvatar';
|
||||
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||
import { getMouseEventCords } from '../../utils/dom';
|
||||
@@ -38,6 +36,64 @@ function formatReadTs(ts: number, hour24Clock: boolean): string {
|
||||
: `${timeMon(ts)} ${timeDay(ts)} ${timeYear(ts)} at ${timeStr}`;
|
||||
}
|
||||
|
||||
type EventReaderItemProps = {
|
||||
room: Room;
|
||||
readerId: string;
|
||||
hour24Clock: boolean;
|
||||
lotusTerminal: boolean;
|
||||
onSelect: React.MouseEventHandler<HTMLButtonElement>;
|
||||
};
|
||||
function EventReaderItem({
|
||||
room,
|
||||
readerId,
|
||||
hour24Clock,
|
||||
lotusTerminal,
|
||||
onSelect,
|
||||
}: EventReaderItemProps) {
|
||||
const { name, avatarUrl } = useMemberAvatar(room, readerId, 100, 100);
|
||||
const receiptTs = room.getReadReceiptForUserId(readerId)?.data.ts;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
style={{ padding: `0 ${config.space.S200}` }}
|
||||
radii="400"
|
||||
onClick={onSelect}
|
||||
before={
|
||||
<Avatar size="200">
|
||||
<UserAvatar
|
||||
userId={readerId}
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
renderFallback={() => <Icon size="50" src={Icons.User} filled />}
|
||||
/>
|
||||
</Avatar>
|
||||
}
|
||||
>
|
||||
<Box direction="Column" grow="Yes">
|
||||
<Text size="T400" truncate>
|
||||
{name}
|
||||
</Text>
|
||||
{receiptTs !== undefined && (
|
||||
<Text
|
||||
size="T200"
|
||||
priority="300"
|
||||
style={
|
||||
lotusTerminal
|
||||
? {
|
||||
color: 'var(--lt-accent-amber)',
|
||||
textShadow: 'var(--lt-glow-amber)',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{formatReadTs(receiptTs, hour24Clock)}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
export type EventReadersProps = {
|
||||
room: Room;
|
||||
eventId: string;
|
||||
@@ -46,7 +102,6 @@ export type EventReadersProps = {
|
||||
export const EventReaders = as<'div', EventReadersProps>(
|
||||
({ className, room, eventId, requestClose, ...props }, ref) => {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const myUserId = mx.getUserId();
|
||||
const latestEventReaders = useRoomEventReaders(room, eventId).filter((id) => id !== myUserId);
|
||||
const openProfile = useOpenUserRoomProfile();
|
||||
@@ -54,9 +109,6 @@ export const EventReaders = as<'div', EventReadersProps>(
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
||||
|
||||
const getName = (userId: string) =>
|
||||
getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId;
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={classNames(css.EventReaders, className)}
|
||||
@@ -100,64 +152,24 @@ export const EventReaders = as<'div', EventReadersProps>(
|
||||
<Box grow="Yes">
|
||||
<Scroll visibility="Hover" hideTrack size="300">
|
||||
<Box className={css.Content} direction="Column">
|
||||
{latestEventReaders.map((readerId) => {
|
||||
const name = getName(readerId);
|
||||
const avatarMxcUrl = room.getMember(readerId)?.getMxcAvatarUrl();
|
||||
const avatarUrl = avatarMxcUrl
|
||||
? (mxcUrlToHttp(mx, avatarMxcUrl, useAuthentication, 100, 100, 'crop') ??
|
||||
undefined)
|
||||
: undefined;
|
||||
const receiptTs = room.getReadReceiptForUserId(readerId)?.data.ts;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={readerId}
|
||||
style={{ padding: `0 ${config.space.S200}` }}
|
||||
radii="400"
|
||||
onClick={(event) => {
|
||||
openProfile(
|
||||
room.roomId,
|
||||
space?.roomId,
|
||||
readerId,
|
||||
getMouseEventCords(event.nativeEvent),
|
||||
'Bottom',
|
||||
);
|
||||
}}
|
||||
before={
|
||||
<Avatar size="200">
|
||||
<UserAvatar
|
||||
userId={readerId}
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
renderFallback={() => <Icon size="50" src={Icons.User} filled />}
|
||||
/>
|
||||
</Avatar>
|
||||
}
|
||||
>
|
||||
<Box direction="Column" grow="Yes">
|
||||
<Text size="T400" truncate>
|
||||
{name}
|
||||
</Text>
|
||||
{receiptTs !== undefined && (
|
||||
<Text
|
||||
size="T200"
|
||||
priority="300"
|
||||
style={
|
||||
lotusTerminal
|
||||
? {
|
||||
color: 'var(--lt-accent-amber)',
|
||||
textShadow: 'var(--lt-glow-amber)',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{formatReadTs(receiptTs, hour24Clock)}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
{latestEventReaders.map((readerId) => (
|
||||
<EventReaderItem
|
||||
key={readerId}
|
||||
room={room}
|
||||
readerId={readerId}
|
||||
hour24Clock={hour24Clock}
|
||||
lotusTerminal={lotusTerminal}
|
||||
onSelect={(event) => {
|
||||
openProfile(
|
||||
room.roomId,
|
||||
space?.roomId,
|
||||
readerId,
|
||||
getMouseEventCords(event.nativeEvent),
|
||||
'Bottom',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Scroll>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user