- Updated `useUserColor` hook to support fetching and embedding user color from avatar metadata with authentication. - Introduced `useUserBanner` hook for managing user banners stored in avatar metadata, including fetching and embedding functionality. - Added `fetchAndExtractMetadata` utility to retrieve both color and banner from images. - Implemented `CustomStatusDialog` component for users to set and clear custom status messages. - Modified `SettingsTab` to include custom status functionality and improved user experience. - Enhanced image metadata utilities to support banner extraction and embedding in PNG format. - Updated sidebar settings to handle user profile changes more gracefully.
181 lines
5.5 KiB
TypeScript
181 lines
5.5 KiB
TypeScript
import React, { useState, useCallback, ChangeEventHandler } from 'react';
|
|
import {
|
|
Box,
|
|
Button,
|
|
config,
|
|
Dialog,
|
|
Input,
|
|
Text,
|
|
Overlay,
|
|
OverlayBackdrop,
|
|
OverlayCenter,
|
|
} from 'folds';
|
|
import FocusTrap from 'focus-trap-react';
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
import { useUserPresence } from '../../hooks/useUserPresence';
|
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
|
|
|
/**
|
|
* Props for the CustomStatusDialog component
|
|
*/
|
|
export interface CustomStatusDialogProps {
|
|
/** Whether the dialog is currently open */
|
|
open: boolean;
|
|
/** Callback function invoked when the dialog should close */
|
|
onClose: () => void;
|
|
}
|
|
|
|
/**
|
|
* CustomStatusDialog Component
|
|
*
|
|
* Provides a dialog interface for users to set or clear their custom status message.
|
|
* The status is stored in Matrix presence and will be visible to other users.
|
|
*
|
|
* @param props - Component props
|
|
* @returns The custom status dialog component
|
|
*/
|
|
export function CustomStatusDialog({ open, onClose }: CustomStatusDialogProps) {
|
|
const mx = useMatrixClient();
|
|
const userId = mx.getUserId();
|
|
const presence = useUserPresence(userId ?? '');
|
|
|
|
const [statusText, setStatusText] = useState(presence?.status || '');
|
|
|
|
/**
|
|
* Handles input changes for the status text field
|
|
* @param evt - The change event from the input
|
|
*/
|
|
const handleChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
|
|
setStatusText(evt.currentTarget.value);
|
|
};
|
|
|
|
/**
|
|
* Async callback to save the status to Matrix presence
|
|
*/
|
|
const [saveState, saveStatus] = useAsyncCallback(
|
|
useCallback(async () => {
|
|
await mx.setPresence({
|
|
presence: presence?.presence || 'online',
|
|
status_msg: statusText,
|
|
});
|
|
onClose();
|
|
}, [mx, presence?.presence, statusText, onClose])
|
|
);
|
|
|
|
/**
|
|
* Async callback to clear the status from Matrix presence
|
|
*/
|
|
const [clearState, clearStatus] = useAsyncCallback(
|
|
useCallback(async () => {
|
|
await mx.setPresence({
|
|
presence: presence?.presence || 'online',
|
|
status_msg: '',
|
|
});
|
|
setStatusText('');
|
|
onClose();
|
|
}, [mx, presence?.presence, onClose])
|
|
);
|
|
|
|
/**
|
|
* Handles form submission to save the custom status
|
|
* @param evt - The form submit event
|
|
*/
|
|
const handleSubmit = (evt: React.FormEvent) => {
|
|
evt.preventDefault();
|
|
saveStatus();
|
|
};
|
|
|
|
/**
|
|
* Handles the clear status button click
|
|
*/
|
|
const handleClear = () => {
|
|
clearStatus();
|
|
};
|
|
|
|
if (!open) return null;
|
|
|
|
return (
|
|
<Overlay open backdrop={<OverlayBackdrop />}>
|
|
<OverlayCenter>
|
|
<FocusTrap
|
|
focusTrapOptions={{
|
|
initialFocus: false,
|
|
onDeactivate: onClose,
|
|
clickOutsideDeactivates: true,
|
|
}}
|
|
>
|
|
<Dialog>
|
|
<Box
|
|
as="form"
|
|
onSubmit={handleSubmit}
|
|
direction="Column"
|
|
gap="400"
|
|
style={{ padding: config.space.S400 }}
|
|
>
|
|
<Box direction="Column" gap="100">
|
|
<Text size="H4">Set Custom Status</Text>
|
|
<Text size="T300" priority="400">
|
|
Your custom status will be visible to other users
|
|
</Text>
|
|
</Box>
|
|
|
|
<Box direction="Column" gap="200">
|
|
<Input
|
|
value={statusText}
|
|
onChange={handleChange}
|
|
placeholder="What's on your mind?"
|
|
maxLength={100}
|
|
autoFocus
|
|
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
|
/>
|
|
{statusText.length > 0 && (
|
|
<Text size="T200" priority="300">
|
|
{statusText.length}/100 characters
|
|
</Text>
|
|
)}
|
|
</Box>
|
|
|
|
{(saveState.status === AsyncStatus.Error || clearState.status === AsyncStatus.Error) && (
|
|
<Text size="T300" priority="400" style={{ color: 'var(--color-critical-main)' }}>
|
|
Failed to update status. Please try again.
|
|
</Text>
|
|
)}
|
|
|
|
<Box direction="Row" gap="200" justifyContent="End">
|
|
<Button
|
|
type="button"
|
|
variant="Secondary"
|
|
onClick={onClose}
|
|
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
|
>
|
|
<Text size="B400">Cancel</Text>
|
|
</Button>
|
|
{presence?.status && (
|
|
<Button
|
|
type="button"
|
|
variant="Critical"
|
|
fill="None"
|
|
onClick={handleClear}
|
|
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
|
>
|
|
<Text size="B400">Clear Status</Text>
|
|
</Button>
|
|
)}
|
|
<Button
|
|
type="submit"
|
|
variant="Primary"
|
|
disabled={saveState.status === AsyncStatus.Loading || clearState.status === AsyncStatus.Loading}
|
|
>
|
|
<Text size="B400">
|
|
{saveState.status === AsyncStatus.Loading ? 'Saving...' : 'Save'}
|
|
</Text>
|
|
</Button>
|
|
</Box>
|
|
</Box>
|
|
</Dialog>
|
|
</FocusTrap>
|
|
</OverlayCenter>
|
|
</Overlay>
|
|
);
|
|
}
|