Manila folders, ruled chat, post-it nav, and folder tabs; dark variant uses Catppuccin Mocha colors with muted stickies and soft glows.
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import React, { useCallback } from 'react';
|
|
import { Box, Chip, Spinner, Text, Tooltip, TooltipProvider, color, config, toRem } from 'folds';
|
|
import { Icon, Icons } from '../../components/icons';
|
|
import { MatrixError, Room } from 'matrix-js-sdk';
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
|
|
|
/**
|
|
* Extract server name from a room ID (!abc:server.com => server.com)
|
|
*/
|
|
function getServerFromRoomId(roomId: string): string {
|
|
const parts = roomId.split(':');
|
|
return parts.length > 1 ? parts.slice(1).join(':') : '';
|
|
}
|
|
|
|
type UnjoinedSubRoomItemProps = {
|
|
roomId: string;
|
|
depth: number;
|
|
isLast?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Displays an unjoined sub-room with a Join button.
|
|
* Used when a parent room has sub-rooms that the user hasn't joined yet.
|
|
*/
|
|
export function UnjoinedSubRoomItem({ roomId, depth, isLast = false }: UnjoinedSubRoomItemProps) {
|
|
const mx = useMatrixClient();
|
|
const viaServers = [getServerFromRoomId(roomId)].filter(Boolean);
|
|
|
|
const [joinState, join] = useAsyncCallback<Room, MatrixError, []>(
|
|
useCallback(() => mx.joinRoom(roomId, { viaServers }), [mx, roomId, viaServers])
|
|
);
|
|
|
|
const canJoin = joinState.status === AsyncStatus.Idle || joinState.status === AsyncStatus.Error;
|
|
const paddingLeft = depth > 0 ? '30px' : undefined;
|
|
|
|
let treeIcon = '';
|
|
if (depth > 0) {
|
|
treeIcon = isLast ? '╰' : '├';
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
data-nav-depth={depth}
|
|
style={{
|
|
paddingLeft,
|
|
padding: `${config.space.S100} ${config.space.S200}`,
|
|
minHeight: '1.5rem',
|
|
position: 'relative',
|
|
}}
|
|
alignItems="Center"
|
|
gap="200"
|
|
>
|
|
{treeIcon && (
|
|
<span style={{
|
|
paddingRight: '2px',
|
|
opacity: 0.5,
|
|
fontFamily: 'monospace',
|
|
fontSize: '14px',
|
|
lineHeight: '1',
|
|
userSelect: 'none',
|
|
marginLeft: '-10px'
|
|
}}>
|
|
{treeIcon}
|
|
</span>
|
|
)}
|
|
<Box grow="Yes" alignItems="Center" gap="100">
|
|
<Text size="T300" truncate style={{ opacity: 0.7 }}>
|
|
{roomId}
|
|
</Text>
|
|
</Box>
|
|
<Box shrink="No" gap="100" alignItems="Center">
|
|
{joinState.status === AsyncStatus.Error && (
|
|
<TooltipProvider
|
|
tooltip={
|
|
<Tooltip variant="Critical" style={{ maxWidth: toRem(200) }}>
|
|
<Text style={{ wordBreak: 'break-word' }} size="T300">
|
|
{joinState.error.data?.error || joinState.error.message}
|
|
</Text>
|
|
</Tooltip>
|
|
}
|
|
>
|
|
{(triggerRef) => (
|
|
<Icon
|
|
ref={triggerRef}
|
|
style={{ color: color.Critical.Main, cursor: 'pointer' }}
|
|
src={Icons.Warning}
|
|
size="200"
|
|
/>
|
|
)}
|
|
</TooltipProvider>
|
|
)}
|
|
<Chip
|
|
variant="Secondary"
|
|
fill="Soft"
|
|
size="400"
|
|
radii="Pill"
|
|
before={
|
|
canJoin ? <Icon src={Icons.Plus} size="50" /> : <Spinner variant="Secondary" size="50" />
|
|
}
|
|
onClick={join}
|
|
disabled={!canJoin}
|
|
>
|
|
<Text size="B300">Join</Text>
|
|
</Chip>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|