Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/app/features/room-nav/RoomNavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { nameInitials } from '../../utils/common';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomUnread } from '../../state/hooks/unread';
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import { favouriteRoomsAtom } from '../../state/room/favouriteRooms';
import { getPowersLevelFromMatrixEvent, usePowerLevels } from '../../hooks/usePowerLevels';
import { copyToClipboard } from '../../utils/dom';
import { markAsRead } from '../../utils/notifications';
Expand Down Expand Up @@ -59,7 +60,7 @@ import { callChatAtom } from '../../state/callEmbed';
import { useCallPreferencesAtom } from '../../state/hooks/callPreferences';
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
import { livekitSupport } from '../../hooks/useLivekitSupport';
import { StateEvent } from '../../../types/matrix/room';
import { RoomTag, StateEvent } from '../../../types/matrix/room';
import { webRTCSupported } from '../../utils/rtc';

type RoomNavItemMenuProps = {
Expand All @@ -79,6 +80,8 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
const canInvite = permissions.action('invite', mx.getSafeUserId());
const openRoomSettings = useOpenRoomSettings();
const space = useSpaceOptionally();
const favouriteRooms = useAtomValue(favouriteRoomsAtom);
const favourite = favouriteRooms.has(room.roomId);

const [invitePrompt, setInvitePrompt] = useState(false);

Expand All @@ -103,6 +106,14 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
requestClose();
};

const handleToggleFavourite = () => {
const promise = favourite
? mx.deleteRoomTag(room.roomId, RoomTag.Favourite)
: mx.setRoomTag(room.roomId, RoomTag.Favourite, {});
promise.catch(() => undefined);
requestClose();
};

return (
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
{invitePrompt && room && (
Expand All @@ -115,6 +126,17 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
/>
)}
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem
onClick={handleToggleFavourite}
size="300"
after={<Icon size="100" src={Icons.Star} filled={favourite} />}
radii="300"
aria-pressed={favourite}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
{favourite ? 'Remove from Favourites' : 'Add to Favourites'}
</Text>
</MenuItem>
<MenuItem
onClick={handleMarkAsRead}
size="300"
Expand Down
133 changes: 93 additions & 40 deletions src/app/pages/client/direct/Direct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { VirtualTile } from '../../../components/virtualizer';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { favouriteRoomsAtom } from '../../../state/room/favouriteRooms';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { useDirectRooms } from './useDirectRooms';
Expand Down Expand Up @@ -168,11 +169,17 @@ function DirectEmpty() {
}

const DEFAULT_CATEGORY_ID = makeNavCategoryId('direct', 'direct');
const FAVOURITE_CATEGORY_ID = makeNavCategoryId('direct', 'favourite');

type DirectVirtualItem =
| { type: 'category'; id: string; name: string; closed: boolean }
| { type: 'room'; roomId: string };
export function Direct() {
const mx = useMatrixClient();
useNavToActivePathMapper('direct');
const scrollRef = useRef<HTMLDivElement>(null);
const directs = useDirectRooms();
const favouriteRooms = useAtomValue(favouriteRoomsAtom);
const notificationPreferences = useRoomsNotificationPreferencesContext();
const roomToUnread = useAtomValue(roomToUnreadAtom);
const navigate = useNavigate();
Expand All @@ -183,16 +190,47 @@ export function Direct() {
const noRoomToDisplay = directs.length === 0;
const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom());

const sortedDirects = useMemo(() => {
const items = Array.from(directs).sort(factoryRoomIdByActivity(mx));
if (closedCategories.has(DEFAULT_CATEGORY_ID)) {
return items.filter((rId) => roomToUnread.has(rId) || rId === selectedRoomId);
const [favouriteRoomIds, otherRoomIds] = useMemo(() => {
const favs: string[] = [];
const others: string[] = [];
directs.forEach((roomId) => {
if (favouriteRooms.has(roomId)) favs.push(roomId);
else others.push(roomId);
});
return [favs, others];
}, [directs, favouriteRooms]);

const virtualItems = useMemo(() => {
const makeDirectList = (roomIds: string[], closed: boolean): string[] => {
const sorted = Array.from(roomIds).sort(factoryRoomIdByActivity(mx));
return closed
? sorted.filter((rId) => roomToUnread.has(rId) || rId === selectedRoomId)
: sorted;
};

const items: DirectVirtualItem[] = [];

if (favouriteRoomIds.length > 0) {
const closed = closedCategories.has(FAVOURITE_CATEGORY_ID);
items.push({ type: 'category', id: FAVOURITE_CATEGORY_ID, name: 'Favourites', closed });
makeDirectList(favouriteRoomIds, closed).forEach((roomId) =>
items.push({ type: 'room', roomId })
);
}

if (otherRoomIds.length > 0 || favouriteRoomIds.length === 0) {
const closed = closedCategories.has(DEFAULT_CATEGORY_ID);
items.push({ type: 'category', id: DEFAULT_CATEGORY_ID, name: 'Chats', closed });
makeDirectList(otherRoomIds, closed).forEach((roomId) =>
items.push({ type: 'room', roomId })
);
}

return items;
}, [mx, directs, closedCategories, roomToUnread, selectedRoomId]);
}, [mx, favouriteRoomIds, otherRoomIds, closedCategories, roomToUnread, selectedRoomId]);

const virtualizer = useVirtualizer({
count: sortedDirects.length,
count: virtualItems.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 38,
overscan: 10,
Expand Down Expand Up @@ -228,49 +266,64 @@ export function Direct() {
</NavButton>
</NavItem>
</NavCategory>
<NavCategory>
<NavCategoryHeader>
<RoomNavCategoryButton
closed={closedCategories.has(DEFAULT_CATEGORY_ID)}
data-category-id={DEFAULT_CATEGORY_ID}
onClick={handleCategoryClick}
>
Chats
</RoomNavCategoryButton>
</NavCategoryHeader>
<div
style={{
position: 'relative',
height: virtualizer.getTotalSize(),
}}
>
{virtualizer.getVirtualItems().map((vItem) => {
const roomId = sortedDirects[vItem.index];
const room = mx.getRoom(roomId);
if (!room) return null;
const selected = selectedRoomId === roomId;
<NavCategory
style={{
height: virtualizer.getTotalSize(),
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((vItem) => {
const item = virtualItems[vItem.index];
if (!item) return null;

if (item.type === 'category') {
return (
<VirtualTile
virtualItem={vItem}
key={vItem.index}
ref={virtualizer.measureElement}
>
<RoomNavItem
room={room}
selected={selected}
showAvatar
direct
linkPath={getDirectRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
notificationMode={getRoomNotificationMode(
notificationPreferences,
room.roomId
)}
/>
<div
style={{ paddingTop: vItem.index === 0 ? undefined : config.space.S400 }}
>
<NavCategoryHeader>
<RoomNavCategoryButton
closed={item.closed}
data-category-id={item.id}
onClick={handleCategoryClick}
>
{item.name}
</RoomNavCategoryButton>
</NavCategoryHeader>
</div>
</VirtualTile>
);
})}
</div>
}

const room = mx.getRoom(item.roomId);
if (!room) return null;
const selected = selectedRoomId === item.roomId;

return (
<VirtualTile
virtualItem={vItem}
key={vItem.index}
ref={virtualizer.measureElement}
>
<RoomNavItem
room={room}
selected={selected}
showAvatar
direct
linkPath={getDirectRoomPath(getCanonicalAliasOrRoomId(mx, item.roomId))}
notificationMode={getRoomNotificationMode(
notificationPreferences,
room.roomId
)}
/>
</VirtualTile>
);
})}
</NavCategory>
</Box>
</PageNavContent>
Expand Down
Loading
Loading