mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 22:40:29 +03:00
redesign thread selector
This commit is contained in:
parent
67c6785bf3
commit
a6a3ac3b24
8 changed files with 154 additions and 115 deletions
|
|
@ -1,90 +0,0 @@
|
||||||
import { Avatar, Box, Icon, Icons, Text } from 'folds';
|
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { IThreadBundledRelationship, Room } from 'matrix-js-sdk';
|
|
||||||
import { ContainerColor } from '../../styles/ContainerColor.css';
|
|
||||||
import * as css from './styles.css';
|
|
||||||
import { UserAvatar } from '../user-avatar';
|
|
||||||
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
|
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
|
||||||
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
|
||||||
|
|
||||||
export function ThreadSelectorContainer({ children }: { children: ReactNode }) {
|
|
||||||
return <Box className={css.ThreadSelectorContainer}>{children}</Box>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThreadSelectorProps = {
|
|
||||||
room: Room;
|
|
||||||
senderId: string;
|
|
||||||
threadDetail: IThreadBundledRelationship;
|
|
||||||
outlined?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ThreadSelector({ room, senderId, threadDetail, outlined }: ThreadSelectorProps) {
|
|
||||||
const mx = useMatrixClient();
|
|
||||||
const useAuthentication = useMediaAuthentication();
|
|
||||||
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
|
|
||||||
|
|
||||||
const latestEvent = threadDetail.latest_event;
|
|
||||||
const latestSenderId = latestEvent.sender;
|
|
||||||
const latestSenderAvatarMxc = getMemberAvatarMxc(room, latestSenderId);
|
|
||||||
const latestDisplayName =
|
|
||||||
getMemberDisplayName(room, latestSenderId) ??
|
|
||||||
getMxIdLocalPart(latestSenderId) ??
|
|
||||||
latestSenderId;
|
|
||||||
|
|
||||||
const latestEventTs = latestEvent.origin_server_ts;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
className={classNames(
|
|
||||||
css.ThreadSelector,
|
|
||||||
outlined && css.ThreadSectorOutlined,
|
|
||||||
ContainerColor({ variant: 'SurfaceVariant' })
|
|
||||||
)}
|
|
||||||
alignItems="Center"
|
|
||||||
gap="200"
|
|
||||||
>
|
|
||||||
<Box gap="100" alignItems="Inherit">
|
|
||||||
<Avatar size="200" radii="300">
|
|
||||||
<UserAvatar
|
|
||||||
userId={senderId}
|
|
||||||
src={
|
|
||||||
senderAvatarMxc
|
|
||||||
? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
alt={senderId}
|
|
||||||
renderFallback={() => <Icon size="200" src={Icons.User} filled />}
|
|
||||||
/>
|
|
||||||
</Avatar>
|
|
||||||
{latestSenderId && (
|
|
||||||
<Avatar size="200" radii="300">
|
|
||||||
<UserAvatar
|
|
||||||
userId={latestSenderId}
|
|
||||||
src={
|
|
||||||
latestSenderAvatarMxc
|
|
||||||
? mxcUrlToHttp(mx, latestSenderAvatarMxc, useAuthentication, 48, 48, 'crop') ??
|
|
||||||
undefined
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
alt={senderId}
|
|
||||||
renderFallback={() => <Icon size="200" src={Icons.User} filled />}
|
|
||||||
/>
|
|
||||||
</Avatar>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box gap="200" alignItems="Inherit">
|
|
||||||
<Text className={css.ThreadRepliesCount} size="L400">
|
|
||||||
{threadDetail.count} {threadDetail.count === 1 ? 'Reply' : 'Replies'}
|
|
||||||
</Text>
|
|
||||||
<Text size="T200" truncate>
|
|
||||||
{/* TODO: date */}
|
|
||||||
Last Reply by <b>{latestDisplayName}</b> at {new Date(latestEventTs).getTime()}
|
|
||||||
</Text>
|
|
||||||
<Icon size="100" src={Icons.ChevronRight} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { style } from '@vanilla-extract/css';
|
|
||||||
import { color, config } from 'folds';
|
|
||||||
|
|
||||||
export const ThreadSelectorContainer = style({
|
|
||||||
paddingTop: config.space.S100,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ThreadSelector = style({
|
|
||||||
padding: config.space.S200,
|
|
||||||
borderRadius: config.radii.R400,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ThreadSectorOutlined = style({
|
|
||||||
borderWidth: config.borderWidth.B300,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ThreadRepliesCount = style({
|
|
||||||
color: color.Primary.Main,
|
|
||||||
flexShrink: 0,
|
|
||||||
});
|
|
||||||
|
|
@ -127,7 +127,7 @@ import { useAccessiblePowerTagColors, useGetMemberPowerTag } from '../../hooks/u
|
||||||
import { useTheme } from '../../hooks/useTheme';
|
import { useTheme } from '../../hooks/useTheme';
|
||||||
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
|
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
|
||||||
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||||
import { ThreadSelector, ThreadSelectorContainer } from '../../components/thread-selector';
|
import { ThreadSelector, ThreadSelectorContainer } from './message/thread-selector';
|
||||||
|
|
||||||
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
||||||
({ position, className, ...props }, ref) => (
|
({ position, className, ...props }, ref) => (
|
||||||
|
|
@ -1113,7 +1113,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
|
|
||||||
{threadDetail && (
|
{threadDetail && (
|
||||||
<ThreadSelectorContainer>
|
<ThreadSelectorContainer>
|
||||||
<ThreadSelector room={room} senderId={senderId} threadDetail={threadDetail} />
|
<ThreadSelector
|
||||||
|
room={room}
|
||||||
|
threadDetail={threadDetail}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
outlined={messageLayout === MessageLayout.Bubble}
|
||||||
|
/>
|
||||||
</ThreadSelectorContainer>
|
</ThreadSelectorContainer>
|
||||||
)}
|
)}
|
||||||
</Message>
|
</Message>
|
||||||
|
|
|
||||||
105
src/app/features/room/message/thread-selector/ThreadSelector.tsx
Normal file
105
src/app/features/room/message/thread-selector/ThreadSelector.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { Avatar, Box, Icon, Icons, Line, Text } from 'folds';
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { IThreadBundledRelationship, Room } from 'matrix-js-sdk';
|
||||||
|
import * as css from './styles.css';
|
||||||
|
import { UserAvatar } from '../../../../components/user-avatar';
|
||||||
|
import { getMemberAvatarMxc, getMemberDisplayName } from '../../../../utils/room';
|
||||||
|
import { useMatrixClient } from '../../../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../../../hooks/useMediaAuthentication';
|
||||||
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../../../utils/matrix';
|
||||||
|
import { Time } from '../../../../components/message';
|
||||||
|
|
||||||
|
export function ThreadSelectorContainer({ children }: { children: ReactNode }) {
|
||||||
|
return <Box className={css.ThreadSelectorContainer}>{children}</Box>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThreadSelectorProps = {
|
||||||
|
room: Room;
|
||||||
|
threadDetail: IThreadBundledRelationship;
|
||||||
|
outlined?: boolean;
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ThreadSelector({
|
||||||
|
room,
|
||||||
|
threadDetail,
|
||||||
|
outlined,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
|
}: ThreadSelectorProps) {
|
||||||
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
|
const latestEvent = threadDetail.latest_event;
|
||||||
|
|
||||||
|
const latestSenderId = latestEvent.sender;
|
||||||
|
const latestSenderAvatarMxc = getMemberAvatarMxc(room, latestSenderId);
|
||||||
|
const latestDisplayName =
|
||||||
|
getMemberDisplayName(room, latestSenderId) ??
|
||||||
|
getMxIdLocalPart(latestSenderId) ??
|
||||||
|
latestSenderId;
|
||||||
|
|
||||||
|
const latestEventTs = latestEvent.origin_server_ts;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="button"
|
||||||
|
type="button"
|
||||||
|
className={classNames(css.ThreadSelector, outlined && css.ThreadSectorOutlined)}
|
||||||
|
alignItems="Center"
|
||||||
|
gap="300"
|
||||||
|
>
|
||||||
|
<Box className={css.ThreadRepliesCount} shrink="No" alignItems="Center" gap="100">
|
||||||
|
<Icon size="100" src={Icons.Thread} filled />
|
||||||
|
<Text size="L400">
|
||||||
|
{threadDetail.count} {threadDetail.count === 1 ? 'Thread Reply' : 'Thread Replies'}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
{latestSenderId && (
|
||||||
|
<>
|
||||||
|
<Line
|
||||||
|
className={css.ThreadSelectorDivider}
|
||||||
|
direction="Vertical"
|
||||||
|
variant="SurfaceVariant"
|
||||||
|
/>
|
||||||
|
<Box gap="200" alignItems="Inherit">
|
||||||
|
<Box gap="100" alignItems="Inherit">
|
||||||
|
<Avatar size="200" radii="400">
|
||||||
|
<UserAvatar
|
||||||
|
userId={latestSenderId}
|
||||||
|
src={
|
||||||
|
latestSenderAvatarMxc
|
||||||
|
? mxcUrlToHttp(
|
||||||
|
mx,
|
||||||
|
latestSenderAvatarMxc,
|
||||||
|
useAuthentication,
|
||||||
|
48,
|
||||||
|
48,
|
||||||
|
'crop'
|
||||||
|
) ?? undefined
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
alt={latestSenderId}
|
||||||
|
renderFallback={() => <Icon size="200" src={Icons.User} filled />}
|
||||||
|
/>
|
||||||
|
</Avatar>
|
||||||
|
</Box>
|
||||||
|
<Text size="T200" truncate>
|
||||||
|
<span>
|
||||||
|
Latest by <strong>{latestDisplayName}</strong> at{' '}
|
||||||
|
</span>
|
||||||
|
<Time
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
ts={latestEventTs}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
<Icon size="100" src={Icons.ChevronRight} />
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
src/app/features/room/message/thread-selector/styles.css.ts
Normal file
37
src/app/features/room/message/thread-selector/styles.css.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
import { color, config, toRem } from 'folds';
|
||||||
|
import { ContainerColor } from '../../../../styles/ContainerColor.css';
|
||||||
|
|
||||||
|
export const ThreadSelectorContainer = style({
|
||||||
|
marginTop: config.space.S200,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ThreadSelector = style([
|
||||||
|
ContainerColor({ variant: 'SurfaceVariant' }),
|
||||||
|
{
|
||||||
|
padding: `${config.space.S200} ${config.space.S300}`,
|
||||||
|
borderRadius: config.radii.R400,
|
||||||
|
cursor: 'pointer',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'&:hover, &:focus-visible': {
|
||||||
|
backgroundColor: color.SurfaceVariant.ContainerHover,
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: color.SurfaceVariant.ContainerActive,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ThreadSectorOutlined = style({
|
||||||
|
borderWidth: config.borderWidth.B300,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ThreadSelectorDivider = style({
|
||||||
|
height: toRem(16),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ThreadRepliesCount = style({
|
||||||
|
color: color.Primary.Main,
|
||||||
|
});
|
||||||
|
|
@ -80,7 +80,7 @@ import {
|
||||||
} from '../../../hooks/useMemberPowerTag';
|
} from '../../../hooks/useMemberPowerTag';
|
||||||
import { useRoomCreatorsTag } from '../../../hooks/useRoomCreatorsTag';
|
import { useRoomCreatorsTag } from '../../../hooks/useRoomCreatorsTag';
|
||||||
import { useRoomMyThreads } from '../../../hooks/useRoomThreads';
|
import { useRoomMyThreads } from '../../../hooks/useRoomThreads';
|
||||||
import { ThreadSelector, ThreadSelectorContainer } from '../../../components/thread-selector';
|
import { ThreadSelector, ThreadSelectorContainer } from '../message/thread-selector';
|
||||||
|
|
||||||
type ThreadMessageProps = {
|
type ThreadMessageProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
|
@ -287,9 +287,10 @@ export const ThreadsMenu = forwardRef<HTMLDivElement, ThreadsMenuProps>(
|
||||||
<ThreadSelectorContainer>
|
<ThreadSelectorContainer>
|
||||||
<ThreadSelector
|
<ThreadSelector
|
||||||
room={room}
|
room={room}
|
||||||
senderId={event.getSender()!}
|
|
||||||
threadDetail={threadDetail}
|
threadDetail={threadDetail}
|
||||||
outlined
|
outlined
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
/>
|
/>
|
||||||
</ThreadSelectorContainer>
|
</ThreadSelectorContainer>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const useRoomMyThreads = (room: Room): MatrixEvent[] | undefined => {
|
||||||
null,
|
null,
|
||||||
30,
|
30,
|
||||||
Direction.Backward,
|
Direction.Backward,
|
||||||
ThreadFilterType.My
|
ThreadFilterType.All
|
||||||
),
|
),
|
||||||
[mx, room]
|
[mx, room]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue