fc9ba03943
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
176 lines
6.3 KiB
TypeScript
176 lines
6.3 KiB
TypeScript
import React, { useCallback, useState } from 'react';
|
|
import FocusTrap from 'focus-trap-react';
|
|
import {
|
|
Avatar,
|
|
Box,
|
|
Button,
|
|
Overlay,
|
|
OverlayBackdrop,
|
|
OverlayCenter,
|
|
Spinner,
|
|
Text,
|
|
as,
|
|
} from 'folds';
|
|
import { Room } from 'matrix-js-sdk';
|
|
import { useAtomValue } from 'jotai';
|
|
import { IRoomCreateContent, Membership, StateEvent } from '../../../types/matrix/room';
|
|
import { getMemberDisplayName, getStateEvent } from '../../utils/room';
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
|
import { timeDayMonthYear, timeHourMinute } from '../../utils/time';
|
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
|
import { RoomAvatar } from '../room-avatar';
|
|
import { nameInitials } from '../../utils/common';
|
|
import { useRoomAvatar, useLocalRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
|
import { mDirectAtom } from '../../state/mDirectList';
|
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
|
import { useSetting } from '../../state/hooks/settings';
|
|
import { settingsAtom } from '../../state/settings';
|
|
import { InviteUserPrompt } from '../invite-user-prompt';
|
|
import { RoomTopicViewer } from '../room-topic-viewer';
|
|
import { stopPropagation } from '../../utils/keyboard';
|
|
|
|
export type RoomIntroProps = {
|
|
room: Room;
|
|
};
|
|
|
|
export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => {
|
|
const mx = useMatrixClient();
|
|
const useAuthentication = useMediaAuthentication();
|
|
const { navigateRoom } = useRoomNavigate();
|
|
const mDirects = useAtomValue(mDirectAtom);
|
|
const [invitePrompt, setInvitePrompt] = useState(false);
|
|
const [viewTopic, setViewTopic] = useState(false);
|
|
|
|
const createEvent = getStateEvent(room, StateEvent.RoomCreate);
|
|
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
|
|
const name = useLocalRoomName(room);
|
|
const topic = useRoomTopic(room);
|
|
const avatarHttpUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication) : undefined;
|
|
|
|
const createContent = createEvent?.getContent<IRoomCreateContent>();
|
|
const ts = createEvent?.getTs();
|
|
const creatorId = createEvent?.getSender();
|
|
const creatorName =
|
|
creatorId && (getMemberDisplayName(room, creatorId) ?? getMxIdLocalPart(creatorId));
|
|
const prevRoomId = createContent?.predecessor?.room_id;
|
|
|
|
const [prevRoomState, joinPrevRoom] = useAsyncCallback(
|
|
useCallback(async (roomId: string) => mx.joinRoom(roomId), [mx]),
|
|
);
|
|
|
|
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
|
|
return (
|
|
<Box direction="Column" grow="Yes" gap="500" {...props} ref={ref}>
|
|
<Box>
|
|
<Avatar size="500">
|
|
<RoomAvatar
|
|
roomId={room.roomId}
|
|
src={avatarHttpUrl ?? undefined}
|
|
alt={name}
|
|
renderFallback={() => <Text size="H2">{nameInitials(name)}</Text>}
|
|
/>
|
|
</Avatar>
|
|
</Box>
|
|
<Box direction="Column" gap="300">
|
|
<Box direction="Column" gap="100">
|
|
<Text size="H3" priority="500">
|
|
{name}
|
|
</Text>
|
|
{topic ? (
|
|
<>
|
|
{viewTopic && (
|
|
<Overlay open backdrop={<OverlayBackdrop />}>
|
|
<OverlayCenter>
|
|
<FocusTrap
|
|
focusTrapOptions={{
|
|
initialFocus: false,
|
|
returnFocusOnDeactivate: false,
|
|
clickOutsideDeactivates: true,
|
|
onDeactivate: () => setViewTopic(false),
|
|
escapeDeactivates: stopPropagation,
|
|
}}
|
|
>
|
|
<RoomTopicViewer
|
|
name={name}
|
|
topic={topic}
|
|
requestClose={() => setViewTopic(false)}
|
|
/>
|
|
</FocusTrap>
|
|
</OverlayCenter>
|
|
</Overlay>
|
|
)}
|
|
<Text
|
|
as="button"
|
|
type="button"
|
|
onClick={() => setViewTopic(true)}
|
|
size="T400"
|
|
priority="400"
|
|
style={{
|
|
background: 'none',
|
|
border: 'none',
|
|
padding: 0,
|
|
cursor: 'pointer',
|
|
textAlign: 'left',
|
|
}}
|
|
>
|
|
{topic.topic}
|
|
</Text>
|
|
</>
|
|
) : (
|
|
<Text size="T400" priority="400">
|
|
This is the beginning of conversation.
|
|
</Text>
|
|
)}
|
|
{creatorName && ts && (
|
|
<Text size="T200" priority="300">
|
|
{'Created by '}
|
|
<b>@{creatorName}</b>
|
|
{` on ${timeDayMonthYear(ts)} ${timeHourMinute(ts, hour24Clock)}`}
|
|
</Text>
|
|
)}
|
|
</Box>
|
|
<Box gap="200" wrap="Wrap">
|
|
<Button onClick={() => setInvitePrompt(true)} variant="Secondary" size="300" radii="300">
|
|
<Text size="B300">Invite Member</Text>
|
|
</Button>
|
|
|
|
{invitePrompt && (
|
|
<InviteUserPrompt room={room} requestClose={() => setInvitePrompt(false)} />
|
|
)}
|
|
{typeof prevRoomId === 'string' &&
|
|
(mx.getRoom(prevRoomId)?.getMyMembership() === Membership.Join ? (
|
|
<Button
|
|
onClick={() => navigateRoom(prevRoomId, createContent?.predecessor?.event_id)}
|
|
variant="Success"
|
|
size="300"
|
|
fill="Soft"
|
|
radii="300"
|
|
>
|
|
<Text size="B300">Open Old Room</Text>
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
onClick={() => joinPrevRoom(prevRoomId)}
|
|
variant="Secondary"
|
|
size="300"
|
|
fill="Soft"
|
|
radii="300"
|
|
disabled={prevRoomState.status === AsyncStatus.Loading}
|
|
after={
|
|
prevRoomState.status === AsyncStatus.Loading ? (
|
|
<Spinner size="50" variant="Secondary" fill="Soft" />
|
|
) : undefined
|
|
}
|
|
>
|
|
<Text size="B300">Join Old Room</Text>
|
|
</Button>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
});
|