Files
cinny/src/app/components/event-readers/EventReaders.tsx
T
Lotus Bot 23008670f3
CI / Build & Quality Checks (push) Successful in 10m13s
chore: upgrade i18next 26, prettier 3, fontsource-variable, domhandler 6, lint-staged 17
- i18next 23->26 + react-i18next 15->17
- prettier 2->3, reformat all files
- replace @fontsource/inter with @fontsource-variable/inter 5, update import path
- domhandler 5->6 (aligns with transitive deps)
- lint-staged 16->17
2026-05-21 23:30:50 -04:00

168 lines
5.8 KiB
TypeScript

import React from 'react';
import classNames from 'classnames';
import {
Avatar,
Box,
Header,
Icon,
IconButton,
Icons,
MenuItem,
Scroll,
Text,
as,
config,
} 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 { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../hooks/useSpace';
import { getMouseEventCords } from '../../utils/dom';
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}`;
}
export type EventReadersProps = {
room: Room;
eventId: string;
requestClose: () => void;
};
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();
const space = useSpaceOptionally();
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)}
direction="Column"
{...props}
ref={ref}
>
<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
}
>
<Box grow="Yes">
<Text
size="H3"
style={
lotusTerminal
? {
color: '#00D4FF',
textShadow: '0 0 6px rgba(0,212,255,0.45)',
letterSpacing: '0.05em',
}
: undefined
}
>
Seen by
</Text>
</Box>
<IconButton size="300" onClick={requestClose} aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
<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"
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>
</MenuItem>
);
})}
</Box>
</Scroll>
</Box>
</Box>
);
},
);