import React, { useEffect, useState } from 'react';
import { useAtom } from 'jotai';
import {
Badge,
Box,
Icon,
Icons,
Menu,
MenuItem,
PopOut,
RectCords,
Text,
Tooltip,
TooltipProvider,
color,
config,
toRem,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { SidebarItem, SidebarItemTooltip, SidebarAvatar } from '../../../components/sidebar';
import { stopPropagation } from '../../../utils/keyboard';
import { UserAvatar } from '../../../components/user-avatar';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix';
import { nameInitials } from '../../../utils/common';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { Settings, SettingsPages } from '../../../features/settings';
import { settingsRequestAtom } from '../../../state/settingsRequest';
import { useUserProfile } from '../../../hooks/useUserProfile';
import { Modal500 } from '../../../components/Modal500';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
type PresenceOption = {
id: 'auto' | 'online' | 'idle' | 'dnd' | 'invisible';
label: string;
dotColor: string;
soft?: boolean;
};
const PRESENCE_OPTIONS: PresenceOption[] = [
{ id: 'online', label: 'Online', dotColor: color.Success.Main },
{ id: 'idle', label: 'Idle', dotColor: color.Warning.Main },
{ id: 'dnd', label: 'Do Not Disturb', dotColor: color.Critical.Main },
{ id: 'invisible', label: 'Invisible', dotColor: color.Secondary.Main, soft: true },
{ id: 'auto', label: 'Auto (activity-based)', dotColor: color.Primary.Main },
];
function PresenceDot({ option, size = 10 }: { option: PresenceOption; size?: number }) {
return (
);
}
function presenceVariant(status: string): 'Success' | 'Warning' | 'Critical' | 'Secondary' {
if (status === 'online') return 'Success';
if (status === 'idle') return 'Warning';
if (status === 'dnd') return 'Critical';
return 'Secondary';
}
function PresencePicker() {
const [presenceStatus, setPresenceStatus] = useSetting(settingsAtom, 'presenceStatus');
const [menuAnchor, setMenuAnchor] = useState();
const currentOption =
PRESENCE_OPTIONS.find((o) => o.id === presenceStatus) ?? PRESENCE_OPTIONS[4];
const closeMenu = () => setMenuAnchor(undefined);
return (
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
}}
>
}
>
{/* Presence dot sits in the bottom-right corner of SidebarItem (which is position:relative) */}
{`Status: ${currentOption.label}`}
}
>
{(triggerRef) => (
)}
);
}
export function SettingsTab() {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const userId = mx.getUserId()!;
const profile = useUserProfile(userId);
const [settingsOpen, setSettingsOpen] = useState(false);
// [Gitea #159] Programmatic "open settings at page X" requests.
const [settingsRequest, setSettingsRequest] = useAtom(settingsRequestAtom);
const [initialPage, setInitialPage] = useState();
useEffect(() => {
if (!settingsRequest) return;
const page = {
general: SettingsPages.GeneralPage,
account: SettingsPages.AccountPage,
notifications: SettingsPages.NotificationPage,
devices: SettingsPages.DevicesPage,
}[settingsRequest];
setInitialPage(page);
setSettingsOpen(true);
setSettingsRequest(null);
}, [settingsRequest, setSettingsRequest]);
const displayName = profile.displayName ?? getMxIdLocalPart(userId) ?? userId;
const avatarUrl = profile.avatarUrl
? (mxcUrlToHttp(mx, profile.avatarUrl, useAuthentication, 96, 96, 'crop') ?? undefined)
: undefined;
return (
// SidebarItem already has position:relative in its CSS — the PresencePicker
// button is absolutely positioned inside it, below the avatar.
{(triggerRef) => (
setSettingsOpen(true)}
>
{nameInitials(displayName)}}
/>
)}
{settingsOpen && (
setSettingsOpen(false)} label="Settings">
setSettingsOpen(false)} />
)}
);
}