2023-10-06 13:44:06 +11:00
|
|
|
import React from 'react';
|
|
|
|
|
import classNames from 'classnames';
|
|
|
|
|
import {
|
|
|
|
|
Avatar,
|
|
|
|
|
Box,
|
|
|
|
|
Header,
|
|
|
|
|
Icon,
|
|
|
|
|
IconButton,
|
|
|
|
|
Icons,
|
|
|
|
|
MenuItem,
|
|
|
|
|
Scroll,
|
|
|
|
|
Text,
|
|
|
|
|
as,
|
|
|
|
|
config,
|
|
|
|
|
} from 'folds';
|
2023-10-10 17:07:15 +11:00
|
|
|
import { Room } from 'matrix-js-sdk';
|
2023-10-06 13:44:06 +11:00
|
|
|
import { useRoomEventReaders } from '../../hooks/useRoomEventReaders';
|
|
|
|
|
import { getMemberDisplayName } from '../../utils/room';
|
2026-05-16 01:34:20 -04:00
|
|
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
2023-10-06 13:44:06 +11:00
|
|
|
import * as css from './EventReaders.css';
|
|
|
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { UserAvatar } from '../user-avatar';
|
2024-09-09 18:45:20 +10:00
|
|
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
2025-08-09 17:46:10 +05:30
|
|
|
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
|
|
|
|
import { useSpaceOptionally } from '../../hooks/useSpace';
|
2025-08-16 17:04:46 +05:30
|
|
|
import { getMouseEventCords } from '../../utils/dom';
|
2026-05-16 01:34:20 -04:00
|
|
|
import { useSetting } from '../../state/hooks/settings';
|
|
|
|
|
import { settingsAtom } from '../../state/settings';
|
|
|
|
|
import { today, yesterday, timeHourMinute, timeMon, timeDay, timeYear } from '../../utils/time';
|
|
|
|
|
|
|
|
|
|
function formatReadTs(ts: number, hour24Clock: boolean): string {
|
|
|
|
|
const timeStr = timeHourMinute(ts, hour24Clock);
|
|
|
|
|
if (today(ts)) return `Today at ${timeStr}`;
|
|
|
|
|
if (yesterday(ts)) return `Yesterday at ${timeStr}`;
|
|
|
|
|
const sameYear = timeYear(ts) === timeYear(Date.now());
|
|
|
|
|
return sameYear
|
|
|
|
|
? `${timeMon(ts)} ${timeDay(ts)} at ${timeStr}`
|
|
|
|
|
: `${timeMon(ts)} ${timeDay(ts)} ${timeYear(ts)} at ${timeStr}`;
|
|
|
|
|
}
|
2023-10-06 13:44:06 +11:00
|
|
|
|
|
|
|
|
export type EventReadersProps = {
|
|
|
|
|
room: Room;
|
|
|
|
|
eventId: string;
|
|
|
|
|
requestClose: () => void;
|
|
|
|
|
};
|
|
|
|
|
export const EventReaders = as<'div', EventReadersProps>(
|
|
|
|
|
({ className, room, eventId, requestClose, ...props }, ref) => {
|
|
|
|
|
const mx = useMatrixClient();
|
2024-09-09 18:45:20 +10:00
|
|
|
const useAuthentication = useMediaAuthentication();
|
2026-05-16 01:34:20 -04:00
|
|
|
const myUserId = mx.getUserId();
|
|
|
|
|
const latestEventReaders = useRoomEventReaders(room, eventId).filter((id) => id !== myUserId);
|
2025-08-09 17:46:10 +05:30
|
|
|
const openProfile = useOpenUserRoomProfile();
|
|
|
|
|
const space = useSpaceOptionally();
|
2026-05-16 01:34:20 -04:00
|
|
|
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
|
|
|
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
2023-10-06 13:44:06 +11:00
|
|
|
|
2023-10-10 17:07:15 +11:00
|
|
|
const getName = (userId: string) =>
|
|
|
|
|
getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId;
|
2023-10-06 13:44:06 +11:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box
|
|
|
|
|
className={classNames(css.EventReaders, className)}
|
|
|
|
|
direction="Column"
|
|
|
|
|
{...props}
|
|
|
|
|
ref={ref}
|
|
|
|
|
>
|
2026-05-16 01:34:20 -04:00
|
|
|
<Header
|
|
|
|
|
className={css.Header}
|
|
|
|
|
variant="Surface"
|
|
|
|
|
size="600"
|
|
|
|
|
style={lotusTerminal ? { borderBottom: '1px solid rgba(0,212,255,0.30)', boxShadow: '0 2px 12px rgba(0,212,255,0.08)' } : undefined}
|
|
|
|
|
>
|
2023-10-06 13:44:06 +11:00
|
|
|
<Box grow="Yes">
|
2026-05-16 01:34:20 -04:00
|
|
|
<Text size="H3" style={lotusTerminal ? { color: '#00D4FF', textShadow: '0 0 6px rgba(0,212,255,0.45)', letterSpacing: '0.05em' } : undefined}>Seen by</Text>
|
2023-10-06 13:44:06 +11:00
|
|
|
</Box>
|
2026-05-20 21:54:33 -04:00
|
|
|
<IconButton size="300" onClick={requestClose} aria-label="Close">
|
2023-10-06 13:44:06 +11:00
|
|
|
<Icon src={Icons.Cross} />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Header>
|
|
|
|
|
<Box grow="Yes">
|
|
|
|
|
<Scroll visibility="Hover" hideTrack size="300">
|
|
|
|
|
<Box className={css.Content} direction="Column">
|
2023-10-10 17:07:15 +11:00
|
|
|
{latestEventReaders.map((readerId) => {
|
|
|
|
|
const name = getName(readerId);
|
2025-08-09 17:46:10 +05:30
|
|
|
const avatarMxcUrl = room.getMember(readerId)?.getMxcAvatarUrl();
|
|
|
|
|
const avatarUrl = avatarMxcUrl
|
2026-05-16 01:34:20 -04:00
|
|
|
? mxcUrlToHttp(mx, avatarMxcUrl, useAuthentication, 100, 100, 'crop') ?? undefined
|
2025-08-09 17:46:10 +05:30
|
|
|
: undefined;
|
2026-05-16 01:34:20 -04:00
|
|
|
const receiptTs = room.getReadReceiptForUserId(readerId)?.data.ts;
|
2023-10-06 13:44:06 +11:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<MenuItem
|
2023-10-10 17:07:15 +11:00
|
|
|
key={readerId}
|
2023-10-06 13:44:06 +11:00
|
|
|
style={{ padding: `0 ${config.space.S200}` }}
|
|
|
|
|
radii="400"
|
2025-08-09 17:46:10 +05:30
|
|
|
onClick={(event) => {
|
|
|
|
|
openProfile(
|
|
|
|
|
room.roomId,
|
|
|
|
|
space?.roomId,
|
|
|
|
|
readerId,
|
2025-08-16 17:04:46 +05:30
|
|
|
getMouseEventCords(event.nativeEvent),
|
2025-08-09 17:46:10 +05:30
|
|
|
'Bottom'
|
|
|
|
|
);
|
2023-10-06 13:44:06 +11:00
|
|
|
}}
|
|
|
|
|
before={
|
|
|
|
|
<Avatar size="200">
|
2024-05-31 19:49:46 +05:30
|
|
|
<UserAvatar
|
|
|
|
|
userId={readerId}
|
2026-05-16 01:34:20 -04:00
|
|
|
src={avatarUrl}
|
2024-05-31 19:49:46 +05:30
|
|
|
alt={name}
|
|
|
|
|
renderFallback={() => <Icon size="50" src={Icons.User} filled />}
|
|
|
|
|
/>
|
2023-10-06 13:44:06 +11:00
|
|
|
</Avatar>
|
|
|
|
|
}
|
|
|
|
|
>
|
2026-05-16 01:34:20 -04:00
|
|
|
<Box direction="Column" grow="Yes">
|
|
|
|
|
<Text size="T400" truncate>
|
|
|
|
|
{name}
|
|
|
|
|
</Text>
|
|
|
|
|
{receiptTs !== undefined && (
|
|
|
|
|
<Text
|
|
|
|
|
size="T200"
|
|
|
|
|
style={lotusTerminal
|
|
|
|
|
? { color: '#FFB300', textShadow: '0 0 6px #FFB300, 0 0 14px rgba(255,179,0,0.40)', fontSize: '0.72rem' }
|
|
|
|
|
: { opacity: 0.6 }}
|
|
|
|
|
>
|
|
|
|
|
{formatReadTs(receiptTs, hour24Clock)}
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
2023-10-06 13:44:06 +11:00
|
|
|
</MenuItem>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Box>
|
|
|
|
|
</Scroll>
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|